
Ursachenanalyse und Patch-Validierung von CVE-2023-52356 in libtiff unter Verwendung von AddressSanitizer und GDB.
Dieses Labor analysiert CVE-2023-52356 in libtiff und konzentriert sich auf das Verhalten von
TIFFReadRGBATileExt, wenn es Bildkoordinaten außerhalb der gültigen
Bildgrenzen erhält.
Die Analyse umfasst die Reproduktion des ursprünglichen Absturzes, das Debugging mit AddressSanitizer und GDB, die Ursachenanalyse, die Untersuchung des Upstream-Patches, die Analyse des ursprünglichen Reproducers sowie die Verifizierung der korrigierten Version.
4d0329a451558511triger_input_47Der ursprüngliche Reproducer wurde gegen den verwundbaren libtiff-Build mit aktiviertem AddressSanitizer kompiliert.
Der Reproducer wurde mit der bereitgestellten auslösenden Datei ausgeführt:
LD_LIBRARY_PATH="$PWD/libtiff/build-asan/libtiff" ./poc triger_input_47
Die Ausführung reproduzierte einen Segmentierungsfehler, der durch einen ungültigen
Speicherzugriff (Lesevorgang) verursacht wurde. Der AddressSanitizer-Stacktrace identifizierte den Fehler während einer
memmove-Operation, die von TIFFReadRGBATileExt aufgerufen wurde.
Mit GDB wurde die Absturzbedingung mit den folgenden Laufzeitwerten reproduziert:
row = 34
img.height = 33
tile_ysize = 1
Der verwundbare Code führte dann Folgendes aus:
read_ysize = img.height - row;
Mit den beobachteten Werten lautet diese Berechnung:
33 - 34 = -1
Da read_ysize vorzeichenlos ist, ergab das Ergebnis einen Überlauf (Underflow) zu:
read_ysize = 4294967295
Das entspricht UINT32_MAX.
Der Wert von read_ysize wurde später in der Quellzeigerberechnung
für memmove verwendet.
Unmittelbar vor dem fehlschlagenden memmove zeigte GDB:
read_ysize = 4294967295
read_xsize = 1
tile_ysize = 1
tile_xsize = 1
i_row = 0
raster = 0x7d0ff67e2d40
Der Zielzeiger ergab:
0x7d0ff67e2d40
Das war der Anfang des raster-Puffers.
Der Quellzeiger ergab:
0x7d13f67e2d38
Der berechnete Quellzeiger lag 17179869176 Bytes, ungefähr
16 GiB, jenseits des Anfangs des raster-Puffers.
Die Ausführung des memmove in GDB führte zu:
SIGSEGV, Segmentation fault
Der Backtrace zeigte den folgenden Absturzpfad:
__sanitizer_internal_memmove
__asan_memmove
TIFFReadRGBATileExt at tif_getimage.c:3345
LLVMFuzzerTestOneInput at poc.cc:61
Dies bestätigt, dass der vorzeichenlose Underflow in read_ysize einen
Quelloffset außerhalb der Grenzen erzeugte. Der resultierende ungültige Quellzeiger wurde dann von
memmove verwendet, was einen ungültigen Speicherzugriff (Lesevorgang) und einen
Segmentierungsfehler verursachte.
Der Upstream-Fix fügte eine explizite Grenzwertprüfung vor der verwundbaren
read_ysize-Berechnung hinzu:
if (col >= img.width || row >= img.height)
{
TIFFErrorExtR(tif, TIFFFileName(tif),
"Invalid row/col passed to TIFFReadRGBATile().");
TIFFRGBAImageEnd(&img);
return (0);
}
Mit GDB wurden bei der korrigierten Version die folgenden Laufzeitwerte beobachtet:
row = 34
img.height = 33
col = 0
img.width = 2047
Für diese Werte ergibt die neue Validierungsbedingung wahr, weil:
row >= img.height
34 >= 33
Die Funktion meldete daher:
Invalid row/col passed to TIFFReadRGBATile()
und gab 0 zurück.
Infolgedessen erreichte die Ausführung nicht die verwundbare Berechnung:
read_ysize = img.height - row;
Dies verhindert den in der verwundbaren Version beobachteten vorzeichenlosen Underflow und
stoppt die Verwendung des ungültigen Werts in der späteren memmove-Quellzeigerberechnung.
Der ursprüngliche Reproducer berechnet die Anzahl der Kacheln entlang der Y-Achse falsch.
Der relevante Code übergibt den Rückgabewert von TIFFGetField() direkt als
Y-Koordinate an TIFFComputeTile():
TIFFComputeTile(
in_tif,
0,
TIFFGetField(in_tif, TIFFTAG_IMAGELENGTH, &tile_height),
0,
0)
Mit GDB wurde beobachtet, dass tile_height Folgendes enthielt:
tile_height = 33
Allerdings gab TIFFGetField() Folgendes zurück:
1
Der Rückgabewert zeigt Erfolg an; er ist nicht die Bildhöhe. Daher wird der Aufruf effektiv zu:
TIFFComputeTile(in_tif, 0, 1, 0, 0)
Für dieses TIFF zeigte GDB, dass dieser Aufruf Folgendes zurückgibt:
2047
Dieser Wert ist ein Kachelindex, nicht die Anzahl der Kacheln entlang der Y-Achse.
Der ursprüngliche Reproducer verwendete diesen Wert dann in seiner Berechnung von
num_tiles_y, was zu Folgendem führte:
num_tiles_y = 63
Die Bildabmessungen und Kacheldimensionen sind jedoch:
image_width = 2047
image_height = 33
tile_width = 1
tile_height = 1
Daher ist die korrekte Anzahl der Kacheln entlang der Y-Achse:
num_tiles_y = 33
Der falsche Wert von 63 führt dazu, dass die Schleife mit Y-Werten von
0 bis 62 iteriert, obwohl die Bildhöhe 33 beträgt und die gültigen Y-Koordinaten nur 0 bis 32 sind.
Dies ermöglicht, dass ein ungültiger Wert wie:
row = 34
an TIFFReadRGBATileExt übergeben wird.
Die verwundbare libtiff-Version wies diese Koordinate außerhalb des gültigen Bereichs
nicht zurück, bevor die vorzeichenlose img.height - row-Berechnung durchgeführt wurde. Dadurch konnte die
vom Reproducer erzeugte ungültige API-Eingabe zu einem Speichersicherheitsfehler
werden.
Der vom Maintainer bereitgestellte korrigierte Reproducer berechnet die Anzahl der Kacheln direkt aus den Bild- und Kacheldimensionen.
Für dasselbe TIFF erzeugt die korrigierte Berechnung:
num_tiles_x = 2047
num_tiles_y = 33
Der korrigierte Reproducer wurde gegen denselben verwundbaren libtiff-Build und dieselbe auslösende Datei getestet.
Das Programm wurde mit dem Status 0 beendet, und es wurde kein AddressSanitizer- oder SIGSEGV-Bericht
erzeugt.
Dies unterstützt die Beobachtung des Maintainers, dass der ursprüngliche Reproducer eine falsche Kachelanzahlberechnung enthält. Die verwundbare libtiff-Version verfügte jedoch weiterhin nicht über defensive Zeilen- und Spaltenvalidierung, sodass ungültige API-Koordinaten zu einem Speichersicherheitsfehler führen konnten.
| Reproducer | libtiff-Version | Ergebnis |
|---|---|---|
| Ursprünglicher PoC | Verwundbar (4d0329a4) | Ungültiger Lesezugriff und SIGSEGV in memmove |
| Korrigierter Reproducer | Verwundbar (4d0329a4) | Exit-Status 0, kein ASan/SEGV |
| Ursprünglicher PoC | Korrigiert (51558511) | Ungültige Zeile/Spalte abgewiesen, kein ASan/SEGV |
Der Absturz in TIFFReadRGBATileExt tritt auf, wenn eine Bildkoordinate
außerhalb des gültigen Bereichs die verwundbare libtiff-Implementierung erreicht.
Im ursprünglichen Reproducer führt eine falsche Kachelanzahlberechnung dazu, dass
num_tiles_y als 63 statt des korrekten Werts 33 berechnet wird.
Infolgedessen kann der Reproducer eine Zeile außerhalb des gültigen Bildbereichs an
TIFFReadRGBATileExt übergeben.
In der verwundbaren libtiff-Version wurden die Zeilen- und Spaltenargumente vor der Berechnung nicht validiert:
read_ysize = img.height - row;
Im reproduzierten Fall war row 34, während img.height 33 betrug.
Da read_ysize vorzeichenlos ist, unterlief die Subtraktion zu
UINT32_MAX. Dieser Wert wurde anschließend in der Quellzeigerberechnung
für memmove verwendet, was einen ungültigen Speicherzugriff (Lesevorgang) und einen
Segmentierungsfehler erzeugte.
Der Upstream-Fix fügt eine explizite Zeilen- und Spaltengrenzwertvalidierung vor dieser
Berechnung hinzu. Tests des korrigierten Builds bestätigten, dass dieselbe ungültige
Koordinate abgewiesen wird, bevor der Underflow und memmove auftreten können.
Der vom Maintainer bereitgestellte korrigierte Reproducer wurde ebenfalls gegen den verwundbaren Build mit derselben TIFF-Datei getestet. Er berechnete die korrekte Kachelanzahl und wurde ohne AddressSanitizer- oder SIGSEGV-Bericht abgeschlossen.
Daher belegt diese Analyse nicht, dass die TIFF-Datei allein den Speichersicherheitsfehler auslöst, wenn die API korrekt verwendet wird. Der ursprüngliche Reproducer enthält eine falsche Kachelanzahlberechnung, während die historische libtiff-Implementierung keine defensive Grenzwertvalidierung aufwies und zuließ, dass die resultierende ungültige API-Eingabe zu einem Speichersicherheitsfehler wurde.