Skip to content

bug: heap-buffer-overflow in RLAInput::decode_rle_span (src/rla.imageio/rlainput.cpp:503) #5152

@beilzx

Description

@beilzx

Describe the bug
A heap-buffer-overflow occurs when decoding a malformed RLA file. In decode_rle_span(), the positive RLE run loop writes to the output buffer (*buf = encoded[e]) without properly checking the remaining output buffer space (n). It only checks the input buffer bound (e < elen). This allows the write to continue past the allocated buffer boundary when a large run count exceeds the remaining output capacity, causing a heap overflow.

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:

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 --hash poc

Evidence
AddressSanitizer crash report:

=================================================================
==435572==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5020000262a0 at pc 0x7f2453b0f5bb bp 0x7ffcf4a1cad0 sp 0x7ffcf4a1cac8
WRITE of size 1 at 0x5020000262a0 thread T0
    #0 0x7f2453b0f5ba in OpenImageIO::v3_2_0::RLAInput::decode_rle_span(unsigned char*, int, int, char const*, unsigned long) /src/test/results/fuzzing_openimageio/OpenImageIO/src/rla.imageio/rlainput.cpp:503:22
    #1 0x7f2453b0f5ba in OpenImageIO::v3_2_0::RLAInput::decode_channel_group(int, short, short, int) /src/test/results/fuzzing_openimageio/OpenImageIO/src/rla.imageio/rlainput.cpp:586:24
    #2 0x7f2453b0fc37 in OpenImageIO::v3_2_0::RLAInput::read_native_scanline(int, int, int, int, void*) /src/test/results/fuzzing_openimageio/OpenImageIO/src/rla.imageio/rlainput.cpp:698:14
    #3 0x7f24537acef4 in OpenImageIO::v3_1::ImageInput::read_native_scanlines(int, int, int, int, int, void*) /src/test/results/fuzzing_openimageio/OpenImageIO/src/libOpenImageIO/imageinput.cpp:491:19
    #4 0x7f24537adcac in OpenImageIO::v3_1::ImageInput::read_native_scanlines(int, int, int, int, OpenImageIO::v3_1::span<std::byte, 18446744073709551615ul>) /src/test/results/fuzzing_openimageio/OpenImageIO/src/libOpenImageIO/imageinput.cpp:565:12
    #5 0x7f24537ac2ad in OpenImageIO::v3_1::ImageInput::read_scanlines(int, int, int, int, int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long) /src/test/results/fuzzing_openimageio/OpenImageIO/src/libOpenImageIO/imageinput.cpp:406:20
    #6 0x7f24537b4960 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/OpenImageIO/src/libOpenImageIO/imageinput.cpp:1168:23
    #7 0x7f24538d185a in OpenImageIO::v3_2_0::pvt::compute_sha1(OpenImageIO::v3_1::ImageInput*, int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>&) /src/test/results/fuzzing_openimageio/OpenImageIO/src/libOpenImageIO/printinfo.cpp:59:25
    #8 0x56331c8ebfbb in print_sha1(OpenImageIO::v3_1::ImageInput*, int, int) /src/test/results/fuzzing_openimageio/OpenImageIO/src/iinfo/iinfo.cpp:48:22
    #9 0x56331c8ebfbb in print_info_subimage(int, int, OpenImageIO::v3_1::ImageSpec&, OpenImageIO::v3_1::ImageInput*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /src/test/results/fuzzing_openimageio/OpenImageIO/src/iinfo/iinfo.cpp:294:9
    #10 0x56331c8ebfbb in print_info(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, unsigned long, OpenImageIO::v3_1::ImageInput*, OpenImageIO::v3_1::ImageSpec&, bool, bool, long long&) /src/test/results/fuzzing_openimageio/OpenImageIO/src/iinfo/iinfo.cpp:415:9
    #11 0x56331c8e7b6f in main /src/test/results/fuzzing_openimageio/OpenImageIO/src/iinfo/iinfo.cpp:483:9
    #12 0x7f24528411c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e)
    #13 0x7f245284128a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e)
    #14 0x56331c803aa4 in _start (/src/test/results/fuzzing_openimageio/OpenImageIO-pre/dist/bin/iinfo+0x3aaa4) (BuildId: 52f4c84cf1f502d429df6f90de919568cfcbe881)

0x5020000262a0 is located 0 bytes after 16-byte region [0x502000026290,0x5020000262a0)
freed by thread T0 here:
    #0 0x56331c8e5bd6 in operator delete(void*, unsigned long) (/src/test/results/fuzzing_openimageio/OpenImageIO-pre/dist/bin/iinfo+0x11cbd6) (BuildId: 52f4c84cf1f502d429df6f90de919568cfcbe881)
    #1 0x7f24521521d0 in std::_Sp_counted_ptr<YAML::detail::node*, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (/usr/local/lib/libOpenColorIO.so.2.4+0x54f1d0) (BuildId: 5ca8611775669a78999cb0e2383351c9111fd537)

previously allocated by thread T0 here:
    #0 0x56331c8e4f51 in operator new(unsigned long) (/src/test/results/fuzzing_openimageio/OpenImageIO-pre/dist/bin/iinfo+0x11bf51) (BuildId: 52f4c84cf1f502d429df6f90de919568cfcbe881)
    #1 0x7f2452151c3b in YAML::detail::memory::create_node() (/usr/local/lib/libOpenColorIO.so.2.4+0x54ec3b) (BuildId: 5ca8611775669a78999cb0e2383351c9111fd537)

SUMMARY: AddressSanitizer: heap-buffer-overflow /src/test/results/fuzzing_openimageio/OpenImageIO/src/rla.imageio/rlainput.cpp:503:22 in OpenImageIO::v3_2_0::RLAInput::decode_rle_span(unsigned char*, int, int, char const*, unsigned long)
Shadow bytes around the buggy address:
  0x502000026000: fa fa fd fd fa fa fd fd fa fa fd fa fa fa fd fd
  0x502000026080: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x502000026100: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x502000026180: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x502000026200: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fa
=>0x502000026280: fa fa fd fd[fa]fa fd fd fa fa fd fd fa fa fd fd
  0x502000026300: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x502000026380: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x502000026400: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x502000026480: fa fa fd fd fa fa fd fa fa fa fd fd fa fa fd fd
  0x502000026500: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
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
==435572==ABORTING

PoC
poc.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions