GHSA-pf59-r2mc-x746 on CTRL-OS 26.05
Aliases: GHSA-pf59-r2mc-x746
Packages: openexr
Status: Plausible
Advisory Information
Summary
A crafted deep scanline EXR with layer-prefixed RGB channels can make PyOpenEXR return stale heap data in coalesced deep RGB sample arrays. The issue is reachable through the public default API,
OpenEXR.File(path), on an attacker-controlled EXR file.PyOpenEXR groups
left.R,left.G, andleft.Binto one Python channel namedleftwhenseparate_channels=False. For deep images, it allocates each pixel sample array with RGB lanes, but the lane offset calculation only recognizes exact unprefixed names ("G","B","A"). As a result,left.Gandleft.Bare decoded into lane 0 instead of lanes 1/2, while lanes 1/2 remain stale and are returned to Python.Affected code
In
src/wrappers/python/PyOpenEXR.cpp,PyPart::setDeepSliceData()creates RGB/RGBA-shaped deep sample arrays and then selects the target lane with exact full-name comparisons:size_t channel_offset = 0; if (C._nrgba > 0) { if (!strcmp(c.name(), "G")) channel_offset = 1; else if (!strcmp(c.name(), "B")) channel_offset = 2; else if (!strcmp(c.name(), "A")) channel_offset = 3; }This works for unprefixed
G/B, but not for prefixed channel names such asleft.Gandleft.B. The wrapper already decided to coalesce those channels into theleftRGB key, so the deep slice setup must also use the prefixed channel suffix or thechannelNameToRGBA()result when choosing the lane.Reproduction
From the OpenEXR v3.4.13 test root used for this report:
cd "report/46. OpenEXR v3.4.13 PyOpenEXR deep prefixed RGB stale lane disclosure" ./reproduce.shExpected key output on vulnerable builds:
keys ['left'] outer object (1, 1) inner float16 (64, 3) values [[30.0, 203.25, 3798.0], [31.0, 203.25, 3798.0], ...] nonzero_unwritten_lanes ... count 64 of 128 wrong_gb_lanes 64 of 64The correct
separate_channels=Truecontrol returns separate channels with the expected samples:left.B: [10.0, 11.0, 12.0, ...] left.G: [20.0, 21.0, 22.0, ...] left.R: [30.0, 31.0, 32.0, ...]Why this proves stale lane disclosure:
- The EXR contains same-type deep HALF channels
left.B,left.G, andleft.Rwith known values.- The reproducer grooms NumPy allocations with recognizable half-float bit patterns before opening the file.
- Default
OpenEXR.File(path)returnschannels()["left"].pixels[0,0]as a(64, 3)array where lane 0 contains R samples, but lanes 1/2 contain groomed stale values instead of G/B samples.Validation notes
Verified locally on:
- OpenEXR v3.4.13 original Python build: reproduced,
nonzero_unwritten_lanes count 64 of 128,wrong_gb_lanes 64 of 64.- OpenEXR v3.4.13 ASAN Python build: reproduced with the same observable stale lanes.
- OpenEXR v3.4.13 UBSAN Python build: reproduced with the same observable stale lanes.
- Upstream main commit
6820342930d35c7c81a6b220c4a0aca9c2532542: reproduced with the same observable stale lanes.Impact
A Python application that reads untrusted deep EXR files with PyOpenEXR's default channel coalescing and then logs, serializes, previews, transforms, or returns the resulting NumPy sample arrays may expose stale same-process heap contents. The demonstrated impact is information disclosure plus incorrect G/B lane data.
PoC files
The minimized
poc.zipcontains only:
reproduce.sh— focused reproduction script for local OpenEXR builds.poc_materials/poc/deep_prefixed_left_rgb_half_1x1_64samp.exr— crafted valid deep EXR.poc_materials/repro_read_prefixed.py— defaultOpenEXR.File(path)proof script.poc_materials/repro_read_prefixed_control.py—separate_channels=Truecontrol script.poc_materials/gen_deep_prefixed_rgb.cpp— generator for the PoC EXR.
Updates
2026-08-11 03:51 CEST
Metadata changes:
- Status for package
openexr: “Plausible”
2026-08-11 03:31 CEST
Metadata changes:
- Status for package
openexr: “New”