AcademySoftwareFoundation / AcademySoftwareFoundation/OpenImageIO

bug: heap-buffer-overflow (out-of-bounds write) in TIFFInput::bit_convert (src/tiff.imageio/tiffinput.cpp)

Open
#5,315 3 comments 0 reactions 0 assignees View on GitHub
bug file formats
Dominant language
C++
Stars
2.4k
Forks
698
Avg merge
3d 4h
Merged PRs (30d)
47

Description

**Describe the bug**
OpenImageIO's TIFF reader contains a heap-based buffer overflow (out-of-bounds write) in TIFFInput::bit_convert(), reachable when parsing a crafted tiled TIFF image whose PhotometricInterpretation is Separated/CMYK (4 samples per pixel) combined with a BitsPerSample value less than 8.
When such an image is read, OpenImageIO converts the CMYK data to a 3-channel RGB output internally, but the buffer allocated for the decoded tile is sized according to the number of channels in the file (4), while the unpacking routine that expands the sub-8-bit samples writes based on the reduced output channel count (3). This inconsistency between the buffer's actual size and the number of values written into it results in the write loop running past the end of the allocated buffer, corrupting adjacent heap memory.
The vulnerability is triggered simply by requesting any pixel data or statistics from the crafted file (e.g. via `iinfo --stats`, `iinfo --hash`, or `iconvert`), i.e. any normal use of the library that reads image content rather than just metadata.

**OpenImageIO version and dependencies**
OIIO 3.2.0.1dev | Linux/x86_64
Build compiler: clang 19.1 | C++17/201703
HW features enabled at build: sse2
No CUDA support (disabled / unavailable at build time)
Dependencies: DCMTK NONE, FFmpeg NONE, fmt 12.1.0, Freetype NONE, GIF NONE, Imath 3.1.9, JPEG 80, JXL NONE,
Libheif NONE, libjpeg-turbo NONE, LibRaw NONE, libuhdr NONE, OpenColorIO 2.4.0, OpenCV NONE, OpenEXR 3.1.5,
OpenJPEG NONE, openjph NONE, PNG 1.6.43, Ptex NONE, Ptex NONE, Robinmap 1.4.0, TBB NONE, TIFF 4.5.1, WebP
1.3.2, ZLIB 1.3

**To Reproduce**
Steps to reproduce the behavior:
```bash
git clone https://github.com/AcademySoftwareFoundation/OpenImageIO.git
cd OpenImageIO

make nuke

export CC=clang
export CXX=clang++

export CFLAGS="-fsanitize=address -fno-omit-frame-pointer -g"
export CXXFLAGS="-fsanitize=address -fno-omit-frame-pointer -g"
export LDFLAGS="-fsanitize=address"

make STOP_ON_WARNING=0 \
USE_OPENGL=0 \
USE_QT=0 \
USE_PYTHON=0 \
BUILD_SHARED_LIBS=1 \
LINKSTATIC=0 \
-j$(nproc)

./dist/bin/iinfo --stats poc.tif
```

**Evidence**
AddressSanitizer crash report:
```bash
/src/test/results/fuzzing_openimageio/test/cnvd_report/POC1_tiff_heap_overflow.tif : 1 x 4096, 3 channel, uint1 tiff
=================================================================
==144981==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x527000003100 at pc 0x7fc024ddecf4 bp 0x7fffc2c11170 sp 0x7fffc2c11168
WRITE of size 1 at 0x527000003100 thread T0
#0 0x7fc024ddecf3 in OpenImageIO::v3_2_0::TIFFInput::bit_convert(int, unsigned char const*, int, void*, int) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/tiff.imageio/tiffinput.cpp:1852:38
#1 0x7fc024ddecf3 in OpenImageIO::v3_2_0::TIFFInput::read_native_tile_locked(int, int, int, int, int, OpenImageIO::v3_1::span) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/tiff.imageio/tiffinput.cpp:2450:17
#2 0x7fc024ddefbb in OpenImageIO::v3_2_0::TIFFInput::read_native_tile(int, int, int, int, int, void*) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/tiff.imageio/tiffinput.cpp:2493:12
#3 0x7fc024a3cb42 in OpenImageIO::v3_1::ImageInput::read_tile(int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long, long) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/libOpenImageIO/imageinput.cpp:645:15
#4 0x7fc024de23bb in OpenImageIO::v3_2_0::TIFFInput::read_tile(int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long, long) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/tiff.imageio/tiffinput.cpp:2790:27
#5 0x7fc024a3eacc in OpenImageIO::v3_1::ImageInput::read_tiles(int, int, int, int, int, int, int, int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long, long) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/libOpenImageIO/imageinput.cpp:843:31
#6 0x7fc024de26b4 in OpenImageIO::v3_2_0::TIFFInput::read_tiles(int, int, int, int, int, int, int, int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long, long) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/tiff.imageio/tiffinput.cpp:2821:27
#7 0x7fc024a42319 in OpenImageIO::v3_1::ImageInput::read_image(int, int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long, long, bool (*)(void*, float), void*) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/libOpenImageIO/imageinput.cpp:1150:23
#8 0x7fc024923b16 in OpenImageIO::v3_1::ImageBufImpl::read(int, int, int, int, bool, OpenImageIO::v3_1::TypeDesc, bool (*)(void*, float), void*, OpenImageIO::v3_2_0::DoLock) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/libOpenImageIO/imagebuf.cpp:1465:27
#9 0x7fc02492b5ca in OpenImageIO::v3_1::ImageBuf::read(int, int, bool, OpenImageIO::v3_1::TypeDesc, bool (*)(void*, float), void*) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/libOpenImageIO/imagebuf.cpp:1528:20
#10 0x55d87f997512 in read_input(std::__cxx11::basic_string, std::allocator> const&, OpenImageIO::v3_1::ImageBuf&, int, int) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/iinfo/iinfo.cpp:64:13
#11 0x55d87f997512 in print_stats(std::__cxx11::basic_string, std::allocator> const&, OpenImageIO::v3_1::ImageSpec const&, int, int, bool) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/iinfo/iinfo.cpp:81:10
#12 0x55d87f997512 in print_info_subimage(int, int, OpenImageIO::v3_1::ImageSpec&, OpenImageIO::v3_1::ImageInput*, std::__cxx11::basic_string, std::allocator> const&) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/iinfo/iinfo.cpp:316:18
#13 0x55d87f997512 in print_info(std::__cxx11::basic_string, std::allocator> const&, unsigned long, OpenImageIO::v3_1::ImageInput*, OpenImageIO::v3_1::ImageSpec&, bool, bool, long long&) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/iinfo/iinfo.cpp:421:14
#14 0x55d87f990c32 in main /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/iinfo/iinfo.cpp:492:14
#15 0x7fc023a7f1c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e)
#16 0x7fc023a7f28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e)
#17 0x55d87f8acad4 in _start (/src/test/results/fuzzing_openimageio/test/OpenImageIO/dist/bin/iinfo+0x3aad4) (BuildId: b43058b69e4695b86490b4cc450eda2919868e7c)

0x527000003100 is located 0 bytes after 12288-byte region [0x527000000100,0x527000003100)
allocated by thread T0 here:
#0 0x55d87f98e0a1 in operator new[](unsigned long) (/src/test/results/fuzzing_openimageio/test/OpenImageIO/dist/bin/iinfo+0x11c0a1) (BuildId: b43058b69e4695b86490b4cc450eda2919868e7c)
#1 0x7fc024a3ca8c in OpenImageIO::v3_1::ImageInput::read_tile(int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long, long) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/libOpenImageIO/imageinput.cpp:644:33
#2 0x7fc024de23bb in OpenImageIO::v3_2_0::TIFFInput::read_tile(int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long, long) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/tiff.imageio/tiffinput.cpp:2790:27
#3 0x7fc024a3eacc in OpenImageIO::v3_1::ImageInput::read_tiles(int, int, int, int, int, int, int, int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long, long) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/libOpenImageIO/imageinput.cpp:843:31
#4 0x7fc024de26b4 in OpenImageIO::v3_2_0::TIFFInput::read_tiles(int, int, int, int, int, int, int, int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long, long) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/tiff.imageio/tiffinput.cpp:2821:27
#5 0x7fc024a42319 in OpenImageIO::v3_1::ImageInput::read_image(int, int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long, long, bool (*)(void*, float), void*) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/libOpenImageIO/imageinput.cpp:1150:23
#6 0x7fc024923b16 in OpenImageIO::v3_1::ImageBufImpl::read(int, int, int, int, bool, OpenImageIO::v3_1::TypeDesc, bool (*)(void*, float), void*, OpenImageIO::v3_2_0::DoLock) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/libOpenImageIO/imagebuf.cpp:1465:27
#7 0x7fc02492b5ca in OpenImageIO::v3_1::ImageBuf::read(int, int, bool, OpenImageIO::v3_1::TypeDesc, bool (*)(void*, float), void*) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/libOpenImageIO/imagebuf.cpp:1528:20
#8 0x55d87f997512 in read_input(std::__cxx11::basic_string, std::allocator> const&, OpenImageIO::v3_1::ImageBuf&, int, int) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/iinfo/iinfo.cpp:64:13
#9 0x55d87f997512 in print_stats(std::__cxx11::basic_string, std::allocator> const&, OpenImageIO::v3_1::ImageSpec const&, int, int, bool) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/iinfo/iinfo.cpp:81:10
#10 0x55d87f997512 in print_info_subimage(int, int, OpenImageIO::v3_1::ImageSpec&, OpenImageIO::v3_1::ImageInput*, std::__cxx11::basic_string, std::allocator> const&) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/iinfo/iinfo.cpp:316:18
#11 0x55d87f997512 in print_info(std::__cxx11::basic_string, std::allocator> const&, unsigned long, OpenImageIO::v3_1::ImageInput*, OpenImageIO::v3_1::ImageSpec&, bool, bool, long long&) /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/iinfo/iinfo.cpp:421:14
#12 0x55d87f990c32 in main /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/iinfo/iinfo.cpp:492:14
#13 0x7fc023a7f1c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e)
#14 0x7fc023a7f28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e)
#15 0x55d87f8acad4 in _start (/src/test/results/fuzzing_openimageio/test/OpenImageIO/dist/bin/iinfo+0x3aad4) (BuildId: b43058b69e4695b86490b4cc450eda2919868e7c)

SUMMARY: AddressSanitizer: heap-buffer-overflow /src/test/results/fuzzing_openimageio/test/OpenImageIO/src/tiff.imageio/tiffinput.cpp:1852:38 in OpenImageIO::v3_2_0::TIFFInput::bit_convert(int, unsigned char const*, int, void*, int)
Shadow bytes around the buggy address:
0x527000002e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x527000002f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x527000002f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x527000003000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x527000003080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x527000003100:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x527000003180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x527000003200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x527000003280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x527000003300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x527000003380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==144981==ABORTING
```

**PoC**
[poc.tif](https://github.com/user-attachments/files/29954419/poc.tif)

Contributor guide

Open the contributing guide

Research direction

Start in src/tiff.imageio/tiffinput.cpp at TIFFInput::bit_convert() around line 1852, then trace its callers in read_native_tile_locked() around line 2450. Build with AddressSanitizer and run ./dist/bin/iinfo --stats poc.tif; done means the crafted tiled CMYK, sub-8-bit TIFF no longer produces a heap-buffer-overflow while image data is read.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
computer-graphics
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.