A crafted indexed PSD with transparency metadata triggers a channel-count mismatch when decoded with oiio:RawColor=1 or psd:RawData=1. PSDInput::read_native_scanline() allocates channel_buffers from the stored PSD channel count, but the raw decode branch passes spec.nchannels to interleave_row(). When spec.nchannels is larger than the stored channel count, interleave_row() indexes past channel_buffers.
== asan ==
=================================================================
==933079==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5030004f0f18 at pc 0x754b551d20d9 bp 0x7ffdc052e860 sp 0x7ffdc052e858
READ of size 8 at 0x5030004f0f18 thread T0
#0 0x754b551d20d8 in std::vector<unsigned char, std::allocator<unsigned char>>::data() const /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:1266:42
#1 0x754b5533d357 in void OpenImageIO::v3_1::PSDInput::interleave_row<unsigned char>(unsigned char*, OpenImageIO::v3_1::span<std::vector<unsigned char, std::allocator<unsigned char>> const, 18446744073709551615ul>, int, int) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/psd.imageio/psdinput.cpp:2150:71
#2 0x754b55336e24 in OpenImageIO::v3_1::PSDInput::read_native_scanline(int, int, int, int, void*) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/psd.imageio/psdinput.cpp:816:13
#3 0x754b54bee901 in OpenImageIO::v3_1::ImageInput::read_native_scanlines(int, int, int, int, int, void*) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imageinput.cpp:491:19
#4 0x754b54bef5d9 in OpenImageIO::v3_1::ImageInput::read_native_scanlines(int, int, int, int, int, int, int, void*) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imageinput.cpp:512:16
#5 0x754b54bec819 in OpenImageIO::v3_1::ImageInput::read_native_scanlines(int, int, int, int, int, int, OpenImageIO::v3_1::span<std::byte, 18446744073709551615ul>) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imageinput.cpp:590:12
#6 0x754b54be246f in OpenImageIO::v3_1::ImageInput::read_scanlines(int, int, int, int, int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imageinput.cpp:428:15
#7 0x754b54beb3e3 in OpenImageIO::v3_1::ImageInput::read_image(int, int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long, long, bool (*)(void*, float), void*) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imageinput.cpp:1168:23
#8 0x754b54a298ed in OpenImageIO::v3_1::ImageBufImpl::read(int, int, int, int, bool, OpenImageIO::v3_1::TypeDesc, bool (*)(void*, float), void*, OpenImageIO::v3_1::DoLock) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imagebuf.cpp:1462:27
#9 0x754b54ae34f4 in OpenImageIO::v3_1::ImageBuf::read(int, int, int, int, bool, OpenImageIO::v3_1::TypeDesc, bool (*)(void*, float), void*) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imagebuf.cpp:1537:20
#10 0x65332254abd1 in OpenImageIO::v3_1::OiioTool::ImageRec::read(OpenImageIO::v3_1::OiioTool::ReadPolicy, OpenImageIO::v3_1::basic_string_view<char, std::char_traits<char>>) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/oiiotool/imagerec.cpp:356:27
#11 0x653322569008 in OpenImageIO::v3_1::OiioTool::Oiiotool::read(std::shared_ptr<OpenImageIO::v3_1::OiioTool::ImageRec>, OpenImageIO::v3_1::OiioTool::ReadPolicy, OpenImageIO::v3_1::basic_string_view<char, std::char_traits<char>>) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/oiiotool/oiiotool.cpp:430:20
#12 0x6533225b8042 in OpenImageIO::v3_1::OiioTool::Oiiotool::read(OpenImageIO::v3_1::OiioTool::ReadPolicy, OpenImageIO::v3_1::basic_string_view<char, std::char_traits<char>>) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/oiiotool/oiiotool.h:212:20
#13 0x6533228558da in output_file(OpenImageIO::v3_1::OiioTool::Oiiotool&, OpenImageIO::v3_1::span<char const* const, 18446744073709551615ul>) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/oiiotool/oiiotool.cpp:5903:13
#14 0x6533228ea90d in std::function<void (OpenImageIO::v3_1::span<char const* const, 18446744073709551615ul>)>::operator()(OpenImageIO::v3_1::span<char const* const, 18446744073709551615ul>) const /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_function.h:591:9
#15 0x754b529883b1 in std::function<void (OpenImageIO::v3_1::ArgParse::Arg&, OpenImageIO::v3_1::span<char const* const, 18446744073709551615ul>)>::operator()(OpenImageIO::v3_1::ArgParse::Arg&, OpenImageIO::v3_1::span<char const* const, 18446744073709551615ul>) const /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_function.h:591:9
#16 0x754b528d97b6 in OpenImageIO::v3_1::ArgParse::Impl::parse_args(int, char const**) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libutil/argparse.cpp:537:25
#17 0x754b528d6bb7 in OpenImageIO::v3_1::ArgParse::parse_args(int, char const**) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libutil/argparse.cpp:431:21
#18 0x6533225da351 in OpenImageIO::v3_1::OiioTool::Oiiotool::getargs(int, char**) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/oiiotool/oiiotool.cpp:7478:12
#19 0x6533228ee125 in main /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/oiiotool/oiiotool.cpp:7873:12
#20 0x754b5202a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#21 0x754b5202a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#22 0x653322350f44 in _start (/home/br0nzu/OpenImageIO/v3.1.14.1/install/asan/bin/oiiotool+0x186f44) (BuildId: 9dec75854dc7a091)
0x5030004f0f18 is located 0 bytes after 24-byte region [0x5030004f0f00,0x5030004f0f18)
allocated by thread T0 here:
#0 0x65332242a431 in operator new(unsigned long) (/home/br0nzu/OpenImageIO/v3.1.14.1/install/asan/bin/oiiotool+0x260431) (BuildId: 9dec75854dc7a091)
#1 0x754b553501b6 in std::vector<std::vector<unsigned char, std::allocator<unsigned char>>, std::allocator<std::vector<unsigned char, std::allocator<unsigned char>>>>::_M_default_append(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/vector.tcc:663:34
#2 0x754b55335d07 in OpenImageIO::v3_1::PSDInput::read_native_scanline(int, int, int, int, void*) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/psd.imageio/psdinput.cpp:789:21
#3 0x754b54bee901 in OpenImageIO::v3_1::ImageInput::read_native_scanlines(int, int, int, int, int, void*) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imageinput.cpp:491:19
#4 0x754b54bef5d9 in OpenImageIO::v3_1::ImageInput::read_native_scanlines(int, int, int, int, int, int, int, void*) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imageinput.cpp:512:16
#5 0x754b54bec819 in OpenImageIO::v3_1::ImageInput::read_native_scanlines(int, int, int, int, int, int, OpenImageIO::v3_1::span<std::byte, 18446744073709551615ul>) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imageinput.cpp:590:12
#6 0x754b54be246f in OpenImageIO::v3_1::ImageInput::read_scanlines(int, int, int, int, int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imageinput.cpp:428:15
#7 0x754b54beb3e3 in OpenImageIO::v3_1::ImageInput::read_image(int, int, int, int, OpenImageIO::v3_1::TypeDesc, void*, long, long, long, bool (*)(void*, float), void*) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imageinput.cpp:1168:23
#8 0x754b54a298ed in OpenImageIO::v3_1::ImageBufImpl::read(int, int, int, int, bool, OpenImageIO::v3_1::TypeDesc, bool (*)(void*, float), void*, OpenImageIO::v3_1::DoLock) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imagebuf.cpp:1462:27
#9 0x754b54ae34f4 in OpenImageIO::v3_1::ImageBuf::read(int, int, int, int, bool, OpenImageIO::v3_1::TypeDesc, bool (*)(void*, float), void*) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libOpenImageIO/imagebuf.cpp:1537:20
#10 0x65332254abd1 in OpenImageIO::v3_1::OiioTool::ImageRec::read(OpenImageIO::v3_1::OiioTool::ReadPolicy, OpenImageIO::v3_1::basic_string_view<char, std::char_traits<char>>) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/oiiotool/imagerec.cpp:356:27
#11 0x653322569008 in OpenImageIO::v3_1::OiioTool::Oiiotool::read(std::shared_ptr<OpenImageIO::v3_1::OiioTool::ImageRec>, OpenImageIO::v3_1::OiioTool::ReadPolicy, OpenImageIO::v3_1::basic_string_view<char, std::char_traits<char>>) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/oiiotool/oiiotool.cpp:430:20
#12 0x6533225b8042 in OpenImageIO::v3_1::OiioTool::Oiiotool::read(OpenImageIO::v3_1::OiioTool::ReadPolicy, OpenImageIO::v3_1::basic_string_view<char, std::char_traits<char>>) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/oiiotool/oiiotool.h:212:20
#13 0x6533228558da in output_file(OpenImageIO::v3_1::OiioTool::Oiiotool&, OpenImageIO::v3_1::span<char const* const, 18446744073709551615ul>) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/oiiotool/oiiotool.cpp:5903:13
#14 0x6533228ea90d in std::function<void (OpenImageIO::v3_1::span<char const* const, 18446744073709551615ul>)>::operator()(OpenImageIO::v3_1::span<char const* const, 18446744073709551615ul>) const /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_function.h:591:9
#15 0x754b529883b1 in std::function<void (OpenImageIO::v3_1::ArgParse::Arg&, OpenImageIO::v3_1::span<char const* const, 18446744073709551615ul>)>::operator()(OpenImageIO::v3_1::ArgParse::Arg&, OpenImageIO::v3_1::span<char const* const, 18446744073709551615ul>) const /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_function.h:591:9
#16 0x754b528d97b6 in OpenImageIO::v3_1::ArgParse::Impl::parse_args(int, char const**) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libutil/argparse.cpp:537:25
#17 0x754b528d6bb7 in OpenImageIO::v3_1::ArgParse::parse_args(int, char const**) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/libutil/argparse.cpp:431:21
#18 0x6533225da351 in OpenImageIO::v3_1::OiioTool::Oiiotool::getargs(int, char**) /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/oiiotool/oiiotool.cpp:7478:12
#19 0x6533228ee125 in main /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/oiiotool/oiiotool.cpp:7873:12
#20 0x754b5202a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#21 0x754b5202a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#22 0x653322350f44 in _start (/home/br0nzu/OpenImageIO/v3.1.14.1/install/asan/bin/oiiotool+0x186f44) (BuildId: 9dec75854dc7a091)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/br0nzu/OpenImageIO/v3.1.14.1/src/OpenImageIO-v3.1.14.1/src/psd.imageio/psdinput.cpp:2150:71 in void OpenImageIO::v3_1::PSDInput::interleave_row<unsigned char>(unsigned char*, OpenImageIO::v3_1::span<std::vector<unsigned char, std::allocator<unsigned char>> const, 18446744073709551615ul>, int, int)
Shadow bytes around the buggy address:
0x5030004f0c80: fa fa fd fd fd fd fa fa fd fd fd fa fa fa fd fd
0x5030004f0d00: fd fa fa fa fd fd fd fa fa fa fd fd fd fa fa fa
0x5030004f0d80: fd fd fd fa fa fa fd fd fd fa fa fa fd fd fd fd
0x5030004f0e00: fa fa fd fd fd fd fa fa fd fd fd fa fa fa 00 00
0x5030004f0e80: 00 fa fa fa 00 00 00 00 fa fa fd fd fd fd fa fa
=>0x5030004f0f00: 00 00 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa
0x5030004f0f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5030004f1000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5030004f1080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5030004f1100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5030004f1180: 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
==933079==ABORTING
timeout: the monitored command dumped core
02. psd-rawcolor-indexed-channel-oob/reproduce.sh: line 5: 933076 Aborted env LD_LIBRARY_PATH="$lib:${LD_LIBRARY_PATH:-}" "$@" timeout 20s "$bin" --iconfig oiio:RawColor 1 -i "$POC" -o "$ROOT/artifacts/repro-psd-$label.tif"
rc=134
OpenImageIO PSD RawColor indexed image out-of-bounds read in
interleave_rowSummary
A crafted indexed PSD with transparency metadata triggers a channel-count mismatch when decoded with
oiio:RawColor=1orpsd:RawData=1.PSDInput::read_native_scanline()allocateschannel_buffersfrom the stored PSD channel count, but the raw decode branch passesspec.nchannelstointerleave_row(). Whenspec.nchannelsis larger than the stored channel count,interleave_row()indexes pastchannel_buffers.Affected versions tested
v3.1.14.1, commit559efc6f3cbddfec25c7bcca177c9f0c656f6da8: non-sanitizedoiiotoolcrashes with SIGSEGV.main, commit5beff968edaf66b7730d1f960f98e7cc0f290cfaafter fetchingorigin/mainon July 1, 2026: non-sanitizedoiiotoolaborts withspan.h:221 Contract assertion failed: idx < m_size.Preconditions and impact
oiio:RawColor=1orpsd:RawData=1.PoC
poc.psdpoc.zippoc.zip
Run from the OpenImageIO test root:
report/02.\ psd-rawcolor-indexed-channel-oob/reproduce.shMinimal command:
LD_LIBRARY_PATH=install/orig/lib timeout 20s \ install/orig/bin/oiiotool --iconfig oiio:RawColor 1 \ -i report/02.\ psd-rawcolor-indexed-channel-oob/poc.psd \ -o artifacts/repro-psd-orig.tifVerification results
orig(v3.1.14.1, non-sanitized)rc=139.asan(v3.1.14.1)AddressSanitizer: heap-buffer-overflowatPSDInput::interleave_row<unsigned char>(),psdinput.cpp:2150.ubsan(v3.1.14.1)PSDInput::interleave_row<unsigned char>(),psdinput.cpp:2152.main-orig(main, non-sanitized)span.h:221 Contract assertion failed: idx < m_size,rc=134.main-orig-highrisk(main, non-sanitized)rc=134.origwithoutRawColorrc=0.Root cause
In
src/psd.imageio/psdinput.cpp:read_native_scanline()createschannel_bufferswithm_channels[subimage].size()entries.interleave_row(..., spec.nchannels).interleave_row()loops overc < nchansand readschannel_buffers[c].spec.nchannelsis larger than the number of stored channel buffers, sochannel_buffers[c]is out of bounds.Relevant lines in both tested trees:
psdinput.cpp:787-817andpsdinput.cpp:2145-2152.Suggested fix
m_channels[subimage].size() >= spec.nchannelsbefore raw interleaving, or use the stored channel count for raw interleave and keep the advertised raw channel count consistent with available channel buffers.ASan log