OpenImageIO PSD reader: a crafted layer with a missing color channel drives a NULL pointer dereference when the layer subimage is read
维护者通常 2 天内回复
还没有人认领这个 Issue。
评估
调研方向
Start with src/psd.imageio/psdinput.cpp, especially PSDInput::setup around the layer channel lookups and read_native_scanline around the reported dereference. Trace how layer channel IDs are parsed and compare that path with composite channel validation. Done means a crafted layer missing an expected color channel is rejected or safely handled, with a regression test covering reading its subimage.
由索引模型根据 Issue 内容生成。
描述
Hello, and thank you for maintaining this project.
I found a likely memory-safety issue by static source review (no runtime exploit was developed). The technical details are inline below so this report is self-contained, with a full write-up linked within.
Affected component: OpenImageIO, the Photoshop PSD/PSB image reader (src/psd.imageio/psdinput.cpp).
Tested version(s): v3.2.1.2 at commit 5c4408f545f1da5f30102c5f1c7151dbd9faf522 (latest release, 2026-10-15, annotated tag object bd94ea79d183f2a5043b6a68a1331ad861629cad). Default branch main HEAD 07e3c3e0373551c57e7d8e01353deaa4b3646613 (2026-10-17) was also inspected and carries the same code. Earlier releases that expose PSD layers as subimages are affected as well.
Details:
NULL pointer dereference (CWE-476) in the OpenImageIO PSD reader, reachable under the default build from a crafted PSD when a consumer reads a layer subimage. The PSD reader exposes the merged composite as subimage 0 and each Photoshop layer as a further subimage (m_subimage_count = m_layers.size() + 1, psdinput.cpp:655).
Root cause. For each layer, PSDInput::setup builds the per-channel pointer list m_channels[subimage] by requesting the color channel ids 0..mode_channel_count-1 from layer.channel_id_map using std::map::operator[] (psdinput.cpp:2192-2194, and :2197 for the transparency channel). layer.channel_id_map is a std::map<int16_t, ChannelInfo*> populated from the layer record, where each channel id is a signed 16-bit field read straight from the file (load_layer, psdinput.cpp:1640, map populated at :1646). load_layer accepts any channel count and any set of ids and does not require a layer to contain the color channels its color mode implies. std::map::operator[] inserts a value-initialized entry on a miss, and a value-initialized ChannelInfo* is nullptr, so for any expected id the layer omits, channel_id_map[i] returns nullptr and that null is pushed into m_channels with no check. mode_channel_count is 3 for RGB, 4 for CMYK, 1 for Grayscale/Bitmap/Indexed and 3 for Lab (psdinput.cpp:532).
Sink. When the layer subimage is read, PSDInput::read_native_scanline iterates m_channels[subimage] and dereferences each entry: ChannelInfo& channel_info = *channels[c]; channel_buffers[c].resize(channel_info.row_length); (psdinput.cpp:821-822). When channels[c] is nullptr, reading channel_info.row_length is a 32-bit read at offset 0 of a null pointer, which faults (SIGSEGV). The dereference runs before any color-mode branch, so every color mode with a nonzero mode_channel_count reaches it, and it runs whether or not the rawcolor/rawdata option is set.
Attacker model. A crafted PSD with a valid 8BPS header (for example RGB, depth 8, small dimensions) and one layer whose channel record lists ids that omit an expected color id, for example {0, 1, 3} for an RGB layer (gap at id 2), with the present channels carrying small valid raw data blocks. The only precondition is that the consumer reads a layer subimage (index 1 or higher). Reading only the composite (subimage 0) does not trigger it, because the composite channels come from the validated m_image_data.channel_info array (load_image_data rejects a header channel count below the color mode minimum at psdinput.cpp:2070-2082, and setup indexes the real array at :2163-2165).
Impact. Deterministic read through a null pointer and a process crash, a denial of service for any tool or service that reads PSD layers with OpenImageIO (for example Blender, VFX and compositing tools, thumbnailers and asset pipelines). Not established: no out-of-bounds write, no out-of-bounds read of adjacent memory, no information disclosure, no instruction-pointer control and no code execution. The dereferenced value is always exactly nullptr (value-initialized by std::map::operator[]), not an attacker-chosen address, so the primitive is a fixed null read.
Fix. In PSDInput::setup, replace the layer channel_id_map[i] and channel_id_map[ChannelID_Transparency] lookups with find and reject the layer when an expected channel id is missing, instead of storing a null pointer. Add a null guard in read_native_scanline before *channels[c]. As defense in depth, validate each layer's channel id set against its color mode at parse time, the way load_image_data already validates the composite channel count.
Verification. The full chain was confirmed statically against the released source at the pinned commit: psdinput.cpp:1637-1647 (channel ids read and map populated), :532 (mode_channel_count table), :2192-2197 (operator[] lookups that insert null), :821-822 (null dereference sink), and :2070-2082 and :2163-2165 (the guarded composite path). The same lines are present on the main HEAD. No AddressSanitizer run or live deployment was used in this phase, so the crash is inferred from the source path.
Distinct from prior PSD advisories. CVE-2026-63420 (GHSA-x877-h4xx-5m5j, fixed 2026-09-18) is a heap out-of-bounds read in the PSD row-interleaving code for a crafted indexed PSD with transparency, only under the rawcolor/rawdata option. CVE-2026-63635 is an out-of-bounds read of fixed lookup tables after an invalid color mode passes header validation under the same option. CVE-2022-41684 (GHSA-3rmp-xjxw-cprh) is a 2022 heap out-of-bounds read in older PSD code. CVE-2022-43593 and CVE-2022-43603 are null-pointer dereferences in the DPX and Zfile output close paths, not the PSD reader. This finding is a NULL pointer dereference in setup on the normal non-raw layer path, a distinct function and root cause.
Write-up: https://hackmd.io/@haindpwn/openimageio-psd-layer-channel-null-deref
Reported by HaiND (Nguyen Dinh Hai), Post and Telecommunication Institute of Technology, github.com/Haind03.
- 主要语言
- C++
- 星标
- 2.4k
- 派生
- 718
- 平均合并
- 2 天 18 小时
- 30 天内合并 PR
- 85
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
AcademySoftwareFoundation/OpenImageIO 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 76/100
AcademySoftwareFoundation/OpenImageIO#5662 ·
维护者通常 2 天内回复
-
bug: copy() writes destination channels the source does not have, taking the values from past the source's pixel可能已有人在做 @lgritz 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 35/100
AcademySoftwareFoundation/OpenImageIO#5661 ·
维护者通常 2 天内回复
-
bug: a malformed filename throws std::regex_error out of ImageCache, and catching it deadlocks the file cache可能已有人在做 @lgritz 今天认领。 未关闭
AcademySoftwareFoundation/OpenImageIO#5660 · 已指派 1 人 ·
维护者通常 2 天内回复
-
PSD: layers with a "real user supplied layer mask" (channel -3) stored as Raw fail to load ("unsupported compression")可能已有人在做 @linsen458-spec 今天认领。 未关闭
难度 3/5 1-2 天 新手友好度 72/100
AcademySoftwareFoundation/OpenImageIO#5659 · 1 条评论 ·
维护者通常 2 天内回复
-
bug: deep paste wraps into the next scanline instead of clipping, and ignores chbegin可能已有人在做 @lgritz 于 2 天前认领。 未关闭
难度 4/5 3-5 天 新手友好度 45/100
AcademySoftwareFoundation/OpenImageIO#5649 ·
维护者通常 2 天内回复
查看 AcademySoftwareFoundation/OpenImageIO 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 83/100
TokisanGames/Terrain3D#1073 ·
维护者通常 1 天内回复
-
enhancement
难度 2/5 1-3 小时 新手友好度 68/100
metasequoiaime/msime#10989 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 63/100
KhronosGroup/SPIRV-LLVM-Translator#4151 ·
维护者通常 2 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复
-
documentation good first issue help wanted
难度 2/5 1-3 小时 新手友好度 85/100
vek-language/vek-lang#1 · 1 条评论 ·