Lokakarya Looney Tunables Peningkatan hak istimewa lokal (CVE-2023-4911) (untuk tujuan pendidikan saja)
Dalam komputasi, linker dinamis adalah bagian dari sistem operasi yang memuat dan menghubungkan pustaka bersama yang diperlukan oleh file yang dapat dieksekusi saat dijalankan, dengan menyalin konten pustaka dari penyimpanan persisten ke RAM, mengisi tabel lompat, dan merelokasi pointer.
Misalnya, kami memiliki program yang menggunakan perpustakaan openssl untuk menghitung hash md5:
$ head md5_hash.c
#include <stdio.h>
#include <string.h>
#include <openssl/md5.h>
ld.so mem-parsing biner dan mencoba menemukan perpustakaan yang terkait dengan <openssl/md5.h>
$ ldd md5_hash
linux-vdso.so.1 (0x00007fffa530b000)
libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f19cda00000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f19cd81e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f19ce032000)
Seperti yang bisa kita lihat, ia menemukan perpustakaan kripto yang diperlukan di /lib/x86_64-linux-gnu/libcrypto.so.3 Selama startup program, ia memasukkan kode perpustakaan ini ke dalam RAM proses dan menghubungkan semua referensi ke perpustakaan ini.
Ketika sebuah program dimulai, pemuat ini pertama-tama memeriksa program tersebut untuk menentukan perpustakaan bersama yang diperlukan. Ia kemudian mencari pustaka-pustaka ini, memuatnya ke dalam memori, dan menghubungkannya dengan pustaka yang dapat dieksekusi pada waktu proses. Dalam prosesnya, pemuat dinamis menyelesaikan referensi simbol, seperti referensi fungsi dan variabel, memastikan bahwa semuanya sudah diatur untuk eksekusi program. Mengingat perannya, pemuat dinamis sangat sensitif terhadap keamanan, karena kodenya berjalan dengan hak istimewa yang lebih tinggi ketika pengguna lokal meluncurkan program set-user-ID atau set-group-ID.
Tunables adalah fitur di Perpustakaan GNU C yang memungkinkan pembuat aplikasi dan pengelola distribusi mengubah perilaku perpustakaan runtime agar sesuai dengan beban kerja mereka. Ini diimplementasikan sebagai satu set saklar yang dapat dimodifikasi dengan cara yang berbeda. Metode default saat ini untuk melakukan hal ini adalah melalui variabel lingkungan GLIBC_TUNABLES dengan menyetelnya ke string pasangan nama=nilai yang dipisahkan titik dua. Misalnya, contoh berikut mengaktifkan pemeriksaan malloc dan menetapkan ambang trim malloc menjadi 128 byte:
GLIBC_TUNABLES=glibc.malloc.trim_threshold=128:glibc.malloc.check=3
export GLIBC_TUNABLES
Meneruskan --list-tunables ke pemuat dinamis untuk mencetak semua merdu dengan nilai minimum dan maksimum:
$ /lib64/ld-linux-x86-64.so.2 --list-tunables
glibc.rtld.nns: 0x4 (min: 0x1, max: 0x10)
glibc.elision.skip_lock_after_retries: 3 (min: 0, max: 2147483647)
glibc.malloc.trim_threshold: 0x0 (min: 0x0, max: 0xffffffffffffffff)
glibc.malloc.perturb: 0 (min: 0, max: 255)
glibc.cpu.x86_shared_cache_size: 0x100000 (min: 0x0, max: 0xffffffffffffffff)
glibc.pthread.rseq: 1 (min: 0, max: 1)
glibc.cpu.prefer_map_32bit_exec: 0 (min: 0, max: 1)
glibc.mem.tagging: 0 (min: 0, max: 255)
Pada awal eksekusinya, ld.so memanggil __tunables_init() untuk menjelajahi lingkungan (pada baris 279), mencari variabel GLIBC_TUNABLES (pada baris 282); untuk setiap GLIBC_TUNABLES yang ditemukan, ia membuat salinan variabel ini (pada baris 284), memanggil parse_tunables() untuk memproses dan membersihkan salinan ini (pada baris 286), dan terakhir mengganti GLIBC_TUNABLES asli dengan salinan yang telah dibersihkan ini (pada baris 288 ):
// (GLIBC ld.so sources in ./glibc-2.37/elf/dl-tunables.c)
269 void
270 __tunables_init ( char * * envp )
271 {
272 char * envname = NULL ;
273 char * envval = NULL ;
274 size_t len = 0 ;
275 char * * prev_envp = envp ;
...
279 while (( envp = get_next_env ( envp , & envname , & len , & envval ,
280 & prev_envp )) != NULL )
281 {
282 if ( tunable_is_name ( "GLIBC_TUNABLES" , envname )) // searching for GLIBC_TUNABLES variables
283 {
284 char * new_env = tunables_strdup ( envname );
285 if ( new_env != NULL )
286 parse_tunables ( new_env + len + 1 , envval ); //
287 /* Put in the updated envval. */
288 * prev_envp = new_env ;
289 continue ;
290 }
Argumen pertama parse_tunables() (tunestr) menunjuk ke salinan GLIBC_TUNABLES yang akan segera dibersihkan, sedangkan argumen kedua (valstring) menunjuk ke variabel lingkungan GLIBC_TUNABLES asli (dalam tumpukan). Untuk membersihkan salinan GLIBC_TUNABLES (yang harus dalam bentuk "tunable1= aaa:tunable2=bbb"
), parse_tunables() menghapus semua merdu berbahaya (sXID_ERASE merdu) dari tunestr, namun tetap mempertahankan SXID_IGNORE dan NONE merdu (pada baris 221- 235):
// (GLIBC ld.so sources in ./glibc-2.37/elf/dl-tunables.c)
162 static void
163 parse_tunables ( char * tunestr , char * valstring )
164 {
...
168 char * p = tunestr ;
169 size_t off = 0 ;
170
171 while (true)
172 {
173 char * name = p ;
174 size_t len = 0 ;
175
176 /* First, find where the name ends. */
177 while ( p [ len ] != '=' && p [ len ] != ':' && p [ len ] != ' ' )
178 len ++ ;
179
180 /* If we reach the end of the string before getting a valid name-value
181 pair, bail out. */
182 if ( p [ len ] == ' ' )
183 {
184 if ( __libc_enable_secure )
185 tunestr [ off ] = ' ' ;
186 return ;
187 }
188
189 /* We did not find a valid name-value pair before encountering the
190 colon. */
191 if ( p [ len ] == ':' )
192 {
193 p += len + 1 ;
194 continue ;
195 }
196
197 p += len + 1 ;
198
199 /* Take the value from the valstring since we need to NULL terminate it. */
200 char * value = & valstring [ p - tunestr ];
201 len = 0 ;
202
203 while ( p [ len ] != ':' && p [ len ] != ' ' )
204 len ++ ;
205
206 /* Add the tunable if it exists. */
207 for ( size_t i = 0 ; i < sizeof ( tunable_list ) / sizeof ( tunable_t ); i ++ )
208 {
209 tunable_t * cur = & tunable_list [ i ];
210
211 if ( tunable_is_name ( cur -> name , name ))
212 {
...
219 if ( __libc_enable_secure )
220 {
221 if ( cur -> security_level != TUNABLE_SECLEVEL_SXID_ERASE )
222 {
223 if ( off > 0 )
224 tunestr [ off ++ ] = ':' ;
225
226 const char * n = cur -> name ;
227
228 while ( * n != ' ' )
229 tunestr [ off ++ ] = * n ++ ;
230
231 tunestr [ off ++ ] = '=' ;
232
233 for ( size_t j = 0 ; j < len ; j ++ )
234 tunestr [ off ++ ] = value [ j ];
235 }
236
237 if ( cur -> security_level != TUNABLE_SECLEVEL_NONE )
238 break ;
239 }
240
241 value [ len ] = ' ' ;
242 tunable_initialize ( cur , value );
243 break ;
244 }
245 }
246
247 if ( p [ len ] != ' ' )
248 p += len + 1 ;
249 }
250 }
Sayangnya, jika variabel lingkungan GLIBC_TUNABLES berbentuk "tunable1=tunable2=AAA" (dengan "tunable1" dan "tunable2" adalah merdu SXID_IGNORE, misalnya "glibc.malloc.mxfast"), maka:
selama iterasi pertama "sementara (benar)" di parse_tunables(), seluruh "tunable1=tunable2=AAA" disalin di tempat ke tunestr (pada baris 221-235), sehingga memenuhi tunestr;
pada baris 247-248, p tidak bertambah (p[len] adalah '