From 8677e352e7fb8c3b9f5e8a7fd356fa01c44d5f94 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 1 Jul 2026 15:42:46 -0400 Subject: [PATCH] fix(psd): validate color_mode before RawColor early return validate_header() let the oiio:RawColor/psd:RawData path return true before the switch that rejects unknown m_header.color_mode values, so an out-of-range or "hole" (undocumented) color mode reached setup(), which unconditionally indexes the fixed 10-entry mode_channel_count / mode_channel_names tables with the attacker-controlled value -- a global out-of-bounds read (and, per external report, a path to unbounded/bogus allocation) reachable via the public RawColor config attribute on a crafted PSD. Move the "is this a color mode we know about at all" check ahead of the RawColor early return so it always runs, and keep the existing Duotone/Lab-unsupported-without-raw check as a second pass after the early return, preserving current behavior for every valid mode. Assisted-by: Claude Code / claude-sonnet-5 Signed-off-by: Larry Gritz --- src/psd.imageio/psdinput.cpp | 27 ++++++++++++------ testsuite/psd-colormodes/ref/out.txt | 4 +++ testsuite/psd-colormodes/run.py | 3 ++ .../psd-colormodes/src/bad_colormode.psd | Bin 0 -> 17568 bytes 4 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 testsuite/psd-colormodes/src/bad_colormode.psd diff --git a/src/psd.imageio/psdinput.cpp b/src/psd.imageio/psdinput.cpp index e22617db63..beb511b88e 100644 --- a/src/psd.imageio/psdinput.cpp +++ b/src/psd.imageio/psdinput.cpp @@ -1013,25 +1013,36 @@ PSDInput::validate_header() errorfmt("[Header] invalid depth {}", m_header.depth); return false; } - if (m_WantRaw) - return true; - - //There are other (undocumented) color modes not listed here + // Reject any color mode outside the known set before the RawColor early + // return below: setup() indexes mode_channel_count/mode_channel_names by + // m_header.color_mode unconditionally (even for raw reads), so an + // out-of-table value (or one of the undocumented gaps, e.g. 5 and 6) + // must never reach that point regardless of m_WantRaw. switch (m_header.color_mode) { case ColorMode_Bitmap: case ColorMode_Indexed: case ColorMode_RGB: case ColorMode_Grayscale: case ColorMode_CMYK: - case ColorMode_Multichannel: break; + case ColorMode_Multichannel: case ColorMode_Duotone: - case ColorMode_Lab: - errorfmt("[Header] unsupported color mode {:d}", m_header.color_mode); - return false; + case ColorMode_Lab: break; default: errorfmt("[Header] unrecognized color mode {:d}", m_header.color_mode); return false; } + + if (m_WantRaw) + return true; + + // Duotone and Lab are only supported via the raw path (handled above). + switch (m_header.color_mode) { + case ColorMode_Duotone: + case ColorMode_Lab: + errorfmt("[Header] unsupported color mode {:d}", m_header.color_mode); + return false; + default: break; + } return true; } diff --git a/testsuite/psd-colormodes/ref/out.txt b/testsuite/psd-colormodes/ref/out.txt index 18975023b1..bce87a0473 100644 --- a/testsuite/psd-colormodes/ref/out.txt +++ b/testsuite/psd-colormodes/ref/out.txt @@ -23,6 +23,10 @@ indexed-transparency-0.psd : 200 x 150, 4 channel, uint8 psd indexed-transparency-255.psd : 200 x 150, 4 channel, uint8 psd SHA-1: BEF32B02F44B369E39670DC2E2D2D1578B4998AD indexed-transparency-256-rejected +oiiotool ERROR: read : [Header] unrecognized color mode 20 +failed to open "src/bad_colormode.psd": failed load_header +Full command line was: +> oiiotool --nostderr --info --iconfig oiio:RawColor 1 src/bad_colormode.psd Comparing "pattern2-8-rgb.psd.tif" and "ref/pattern2.tif" PASS Comparing "pattern2-16-rgb.psd.tif" and "ref/pattern2.tif" diff --git a/testsuite/psd-colormodes/run.py b/testsuite/psd-colormodes/run.py index d94641bf96..4dbb444958 100755 --- a/testsuite/psd-colormodes/run.py +++ b/testsuite/psd-colormodes/run.py @@ -35,4 +35,7 @@ + "out.null > /dev/null 2>&1 " + "|| echo indexed-transparency-256-rejected)") +# Regression test: bad colormode +command += oiiotool ("--nostderr --info --iconfig oiio:RawColor 1 src/bad_colormode.psd", failureok=True) + outputs += [ "out.txt" ] diff --git a/testsuite/psd-colormodes/src/bad_colormode.psd b/testsuite/psd-colormodes/src/bad_colormode.psd new file mode 100644 index 0000000000000000000000000000000000000000..3d442899a9b00dbb25d381b59cacc87c791e699b GIT binary patch literal 17568 zcmeHP-*4N-9Y5KQV>`}|1}%!UUB@cxmaUU1%93SqvJ*>oY@<;u$Xe58*aJnLB*qlU zktbWpOY^W{z<>b-)(%)PVBOP({tE-PzV3Mg_PWPnzkcb@slD@^u9Dl5HTzA>;B^#~K8g*&>rk^ajs zf1MU|Z6kfRP|McrD(>og4;_5?@J>^Gc%+uJ^o`A>@<1N+>>ehHFz6YkD-Sl(ie|O2 z40Q^NX@RMbqmA?y6$r1_>O$3Wuuxc8%c#=ox=<{wNQKgRF8?(lmzCDUY)O>znXDvd zOLA5cyeqxA1hulGwdKb4Ew4LhZKS(|*s>^|oSdwjtgcv2N0dsXl9R^RfPSdiCO}_4R-=9#e(seEh*YE14 zW}UcSDT|XP!xb2M!`lkMo76IsEoP+Erj(U)YqFGmh3=E=h!d_k;iCq-QB#N#@*>Ik zoLtC{dKuH5^kZpy`@v&*8ETIB&{Vl?IX#7JDz9oQ3( zbC$W+akEMHWs$~PL;nO8!c~r+Y#B=H`63*aLPk}JIMY`0xlBndwljq`URy1$YO9il z{bl~R%dnc0^O7HNKD}ia?qap8m>sNbivG-e;H*||F0|Cc&b=e_#4GoB5%5V4(8ROi ze_-YKNgR)fekc#e&~or}lBJ0V#@SBR20 zAfYRcoscY{D?~{gkkA#!PDqx}6`~{#Na%`VCnQVg3Q-aVBy`2G6Otu#g(!&w61w8p z3CR+=LX^Y-30-mQgk%X_Axh$agswPtLb8Oe5G8TIXOXVu$O(NGXA(J)2eiNya;=vo@4WnvR@F;5`rl4Whm>?_s3 zhx?V_yVe(MM>okqpV)l@;5n*NcgbN3lJMBKvG+}avDxoY0j;yL*Abw+%xn+!0oADJ zgxXLs_lcc5N)I=8UT*@#zu3(U>W*c#4>9T62d(=m2+wt2I#R!%`R2RS7%ka>99qn< zVi40C!*Z*y8$>sm6Ob=5?P~3fS9#Xz*Qtw{4BUouygD++1D3hzg8W&kS+$Aj4;xjD zmJ>7@9k&)Vs*YJ5HcT>X+-@0eAi2{a$3bJKXKV)zU~cHTq8@d;sdHXXW4BU;(a^+Q z)X)S$v-+)@)*on-UAvAM!YW2sui|LUZL ztB?o>`HGO;>g|Jir|$aufYzxplyunWHg0n_6Wul~JF?qD)!=yDQE$FMJ+SMF>R9$& z$Um#oJ*k@=pT0{}(10;jEJCcFVVNDzWZ9R{A&lS`eY~M}x}$;%z5vD+=+o0N`zJmI zv+^Q^_PwAq;c#@dj}&66ZN9~T3ydu(){3)(L-tG~1M zBD=$l`~EUL4>T{_+j3}z>HdSEdq`}zL1MriMhaKTLw@$-cLOBH@OyCgb?9tTh@LhN zVLtx}ZG>!p)P918Usm?EK`g+B=9b< zU$DmkoH!$>GCWIkD5x?2QD6sp-Tn? z+Yq|kV!=6tUw~!rPO7u>cb#^Gmfe7HT1k1XM>|s1x|OtY(ASvmc=$pep=6wC3-IUI%M#R3EA@O(zn{A9>A(?9N$j9fKTayg>U1`5e@|xzl-m9RKEFdt|9XTF rL};)4$UlC7=wCFDvqf226(lde6!>{B#rvkumG@?N5CH%G1=oK7E_3S> literal 0 HcmV?d00001