
Analisi delle cause profonde e validazione della patch per CVE-2023-52356 in libtiff utilizzando AddressSanitizer e GDB.
Questo laboratorio analizza la CVE-2023-52356 in libtiff, concentrandosi sul comportamento di
TIFFReadRGBATileExt quando riceve coordinate immagine al di fuori dei limiti validi
dell'immagine.
L'analisi include la riproduzione del crash originale, il debug con AddressSanitizer e GDB, l'analisi della causa principale, l'esame della patch upstream, l'analisi del reproducer originale e la verifica della versione corretta.
4d0329a451558511triger_input_47Il reproducer originale è stato compilato contro la build vulnerabile di libtiff con AddressSanitizer abilitato.
Il reproducer è stato eseguito con il file trigger fornito:
LD_LIBRARY_PATH="$PWD/libtiff/build-asan/libtiff" ./poc triger_input_47
L'esecuzione ha riprodotto un segmentation fault causato da una lettura di memoria
non valida. La traccia dello stack di AddressSanitizer ha identificato il guasto durante
un'operazione memmove chiamata da TIFFReadRGBATileExt.
Utilizzando GDB, la condizione del crash è stata riprodotta con i seguenti valori runtime:
row = 34
img.height = 33
tile_ysize = 1
Il codice vulnerabile ha quindi eseguito:
read_ysize = img.height - row;
Con i valori osservati, questo calcolo è:
33 - 34 = -1
Poiché read_ysize è senza segno, il risultato è stato riportato a:
read_ysize = 4294967295
che è UINT32_MAX.
Il valore di read_ysize è stato successivamente utilizzato nel calcolo del puntatore
sorgente per memmove.
Immediatamente prima del memmove fallito, GDB ha mostrato:
read_ysize = 4294967295
read_xsize = 1
tile_ysize = 1
tile_xsize = 1
i_row = 0
raster = 0x7d0ff67e2d40
Il puntatore di destinazione valutato a:
0x7d0ff67e2d40
che era l'inizio del buffer raster.
Il puntatore sorgente valutato a:
0x7d13f67e2d38
Il puntatore sorgente calcolato era 17179869176 byte, circa
16 GiB, oltre l'inizio del buffer raster.
L'esecuzione del memmove in GDB ha prodotto:
SIGSEGV, Segmentation fault
Il backtrace ha mostrato il seguente percorso del crash:
__sanitizer_internal_memmove
__asan_memmove
TIFFReadRGBATileExt at tif_getimage.c:3345
LLVMFuzzerTestOneInput at poc.cc:61
Ciò conferma che l'underflow senza segno in read_ysize ha prodotto un
offset sorgente fuori dai limiti. Il puntatore sorgente non valido risultante è stato
quindi utilizzato da memmove, causando una lettura di memoria non valida e un
segmentation fault.
La correzione upstream ha aggiunto un controllo esplicito dei limiti prima del
calcolo vulnerabile di read_ysize:
if (col >= img.width || row >= img.height)
{
TIFFErrorExtR(tif, TIFFFileName(tif),
"Invalid row/col passed to TIFFReadRGBATile().");
TIFFRGBAImageEnd(&img);
return (0);
}
Utilizzando GDB sulla versione corretta, sono stati osservati i seguenti valori runtime:
row = 34
img.height = 33
col = 0
img.width = 2047
Per questi valori, la nuova condizione di validazione risulta vera perché:
row >= img.height
34 >= 33
La funzione ha quindi riportato:
Invalid row/col passed to TIFFReadRGBATile()
e ha restituito 0.
Di conseguenza, l'esecuzione non ha raggiunto il calcolo vulnerabile:
read_ysize = img.height - row;
Ciò previene l'underflow senza segno osservato nella versione vulnerabile e
impedisce che il valore non valido venga utilizzato nel successivo calcolo del puntatore
sorgente di memmove.
Il reproducer originale calcola erroneamente il numero di tile lungo l'asse Y.
Il codice pertinente passa il valore di ritorno di TIFFGetField() direttamente come
coordinata Y a TIFFComputeTile():
TIFFComputeTile(
in_tif,
0,
TIFFGetField(in_tif, TIFFTAG_IMAGELENGTH, &tile_height),
0,
0)
Utilizzando GDB, è stato osservato che tile_height conteneva:
tile_height = 33
Tuttavia, TIFFGetField() ha restituito:
1
Il valore di ritorno indica il successo; non è l'altezza dell'immagine. Pertanto, la chiamata diventa effettivamente:
TIFFComputeTile(in_tif, 0, 1, 0, 0)
Per questo TIFF, GDB ha mostrato che questa chiamata restituisce:
2047
Questo valore è un indice di tile, non il numero di tile lungo l'asse Y.
Il reproducer originale ha quindi utilizzato questo valore nel suo calcolo di
num_tiles_y, ottenendo:
num_tiles_y = 63
Tuttavia, le dimensioni dell'immagine e le dimensioni delle tile sono:
image_width = 2047
image_height = 33
tile_width = 1
tile_height = 1
Pertanto, il numero corretto di tile lungo l'asse Y è:
num_tiles_y = 33
Il valore errato di 63 fa sì che il ciclo iteri con valori Y da
0 a 62, anche se l'altezza dell'immagine è 33 e le coordinate Y valide
sono solo da 0 a 32.
Ciò consente che un valore non valido come:
row = 34
venga passato a TIFFReadRGBATileExt.
La versione vulnerabile di libtiff non ha respinto questa coordinata fuori
intervallo prima di eseguire il calcolo senza segno img.height - row. Ciò ha permesso
che l'input API non valido generato dal reproducer diventasse un guasto di sicurezza
della memoria.
Il reproducer corretto fornito dal maintainer calcola il numero di tile direttamente dalle dimensioni dell'immagine e delle tile.
Per lo stesso TIFF, il calcolo corretto produce:
num_tiles_x = 2047
num_tiles_y = 33
Il reproducer corretto è stato testato contro la stessa build vulnerabile di libtiff e lo stesso file trigger.
Il programma è terminato con stato 0 e non è stato prodotto alcun report
AddressSanitizer o SIGSEGV.
Ciò supporta l'osservazione del maintainer secondo cui il reproducer originale contiene un calcolo errato del conteggio delle tile. Tuttavia, la versione vulnerabile di libtiff mancava ancora di validazione difensiva di righe e colonne, consentendo a coordinate API non valide di provocare un guasto di sicurezza della memoria.
| Reproducer | Versione libtiff | Risultato |
|---|---|---|
| PoC originale | Vulnerabile (4d0329a4) | Lettura non valida e SIGSEGV in memmove |
| Reproducer corretto | Vulnerabile (4d0329a4) | Stato di uscita 0, nessun ASan/SEGV |
| PoC originale | Corretta (51558511) | Riga/colonna non valida respinta, nessun ASan/SEGV |
Il crash in TIFFReadRGBATileExt si verifica quando una coordinata immagine
fuori intervallo raggiunge l'implementazione vulnerabile di libtiff.
Nel reproducer originale, un calcolo errato del conteggio delle tile fa sì che
num_tiles_y venga calcolato come 63 invece del valore corretto 33.
Di conseguenza, il reproducer può passare una riga al di fuori dell'intervallo valido
dell'immagine a TIFFReadRGBATileExt.
Nella versione vulnerabile di libtiff, gli argomenti di riga e colonna non venivano validati prima del calcolo:
read_ysize = img.height - row;
Per il caso riprodotto, row era 34 mentre img.height era 33.
Poiché read_ysize è senza segno, la sottrazione è andata in underflow a
UINT32_MAX. Questo valore è stato successivamente utilizzato nel calcolo del puntatore
sorgente per memmove, producendo una lettura di memoria non valida e un
segmentation fault.
La correzione upstream aggiunge una validazione esplicita dei limiti di riga e colonna
prima di questo calcolo. Il test della build corretta ha confermato che la stessa
coordinata non valida viene respinta prima che possano verificarsi l'underflow e il
memmove.
Il reproducer corretto fornito dal maintainer è stato anche testato contro la build vulnerabile utilizzando lo stesso file TIFF. Ha calcolato il conteggio corretto delle tile e si è completato senza alcun report AddressSanitizer o SIGSEGV.
Pertanto, questa analisi non stabilisce che il file TIFF da solo inneschi il guasto di sicurezza della memoria quando l'API viene utilizzata correttamente. Il reproducer originale contiene un calcolo errato del conteggio delle tile, mentre l'implementazione storica di libtiff mancava di validazione difensiva dei limiti e consentiva che l'input API non valido risultante diventasse un guasto di sicurezza della memoria.