Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ Streams and rotated images can change geometry after the header.
values into 3-channel buffer). Fix: compare decoder-reported dims, channel
count, and stride against the spec before every copy-out; size scratch
buffers from the decoder-reported values, not the header.
- Colorspace tag selecting a fixed-width conversion: a file-supplied tag
picks a converter that touches N samples per pixel with no check that
nchannels >= N. Find: per-pixel `p[i+1]`/`p[i+2]` in `*_to_rgb` helpers;
trace what gates the call. Seen: jpeg2000 sYCC (`colr` EnumCS 18) on a
1-channel image, 2 samples OOB read+write past every scanline.
Fix: gate on channel count too (`nc >= 3`).
- Uninitialized packet structs: `av_packet_alloc()`, not stack AVPacket,
because `av_read_frame` does not init on failure.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ OIIO validates geometry, checks returns, bounds its own buffers.
were missing from `check_open`; library bounds its own framebuffer (C6 left).
- jpeg2000: two codepaths (OpenJPEG, OpenJPH/HTJ2K) - guard both before
decode; HTJ2K error handling continued after failure; zero components read
past an array; OpenJPH global error handler needs static lifetime.
past an array; OpenJPH global error handler needs static lifetime;
sYCC conversion ran on 1-channel images (OpenJPEG sets SYCC from the
`colr` box alone).
Open: >4 components out of step; truncated codestream decodes silently
(no completeness signal from OpenJPH).
- webp: check_open + C2 moved before the decoded-image allocation; no real
Expand Down
1 change: 1 addition & 0 deletions docs/dev/format-hardening-deferrals.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Newest first. One row per deferred finding.

| Date | Format | Location (file:line / function) | Kind | Description | Why deferred | Severity |
|------|--------|---------------------------------|------|-------------|--------------|----------|
| 2026-10-29 | jpeg2000 | src/jpeg2000.imageio/jpeg2000output.cpp / close() | writer | `close()` calls `save_image()` and ignores its return, so an OpenJPEG encode failure is reported as success. Seen with a 16x8 image (default resolution count too high for that size): `oiiotool` exits 0 and leaves a 77-byte file with no codestream. | Writer-side and not a read hazard; the reader rejects the resulting file cleanly ("Expected a SOC marker"). Fix is to propagate the return, plus possibly clamp `numresolution` to what the image size supports. | P2 |
| 2026-07-26 | xmp | pugixml (`xml_document::load_buffer`, reached from src/libOpenImageIO/xmp.cpp / decode_xmp()) | other | The packet is materialized twice before any cap applies: the caller copies it into a `std::string` and pugixml copies it again into its own buffer plus a node pool. Measured 652 MB peak RSS for a 75 MB packet of tiny elements, i.e. ~8.7x, all of it before `decode_xmp_node()` runs. Linear in the input and bounded by whatever the container hands over, so resource use, not a safety defect. JPEG caps an APP1 marker at 64 KB, but PNG `zTXt`/`iTXt`, TIFF `XMLPACKET`, PSD resources and HEIF are all effectively unbounded. | Capping the packet size in the shared decoder is the same policy question the exif row above raises -- any ceiling risks dropping legitimate XMP, which has no specified upper bound, and a `limits:*`-style metadata ceiling needs maintainer sign-off. The container is the better place to bound the blob, which is a per-format decision. | P2 |
| 2026-07-26 | xmp | src/libOpenImageIO/xmp.cpp / decode_xmp() (callers: src/psd.imageio/psdinput.cpp:1405) | reader-nonblocking | `decode_xmp()` returns `true` unconditionally on every path, so the failure posture (C8) is "best effort, never report" and there is nothing for `imageinput:strict` to gate. PSD's `if (!decode_xmp(...)) { errorfmt(...); return false; }` is therefore dead code, and the new budget/depth caps silently truncate rather than reporting. | Deliberately unchanged in this phase. Giving the function a real `false` return means deciding what counts as a failure for a decoder whose whole design is to tolerate the minor malformations common in real files (see the comment in `decode_xmp`), and PSD would then hard-error on files it reads today. That is a posture change for five containers at once, which wants a maintainer's call. | P2 |
| 2026-07-26 | xmp | src/libOpenImageIO/xmp.cpp / add_attrib() | reader-nonblocking | `oiiotype == TypeRational \|\| string_is_rational(xmlname)` tests the XML attribute *name* for an "int/int" shape, not the value -- almost certainly meant to be `xmlvalue`. As written the branch is nearly unreachable (no table entry is `TypeRational`, and an attribute literally named `1/2` is not well-formed XML), so rational-valued XMP attributes fall through to the catch-all and are stored as strings. The same branch then stores under `xmlname` rather than the mapped `oiioname`. | Not a hazard: `parse_rational` fills the array only on success and the attribute is written only inside that branch, so nothing uninitialized escapes. Fixing the typo changes which attributes valid files publish and under what name and type, which is a metadata-compatibility change, not a hardening one . | P2 |
Expand Down
1 change: 1 addition & 0 deletions src/fuzz/populate_corpora.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def _find_images_root() -> Path:
],
"jpeg2000": [
("testsuite/htj2k/src", ["j2c"]),
("testsuite/jpeg2000/src", ["jp2"]),
("../oiio-images/jpeg2000", ["jp2", "j2k"]),
("../j2kp4files_v1_5/codestreams_profile0", ["j2k"]),
],
Expand Down
2 changes: 1 addition & 1 deletion src/jpeg2000.imageio/jpeg2000input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,7 @@ Jpeg2000Input::copy_scanline(int y, int /*z*/, void* data)
}
}
}
if (m_image->color_space == OPJ_CLRSPC_SYCC)
if (m_image->color_space == OPJ_CLRSPC_SYCC && nc >= 3)
yuv_to_rgb(scanline);
}

Expand Down
11 changes: 11 additions & 0 deletions testsuite/jpeg2000/ref/out-icc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ idiff ERROR: Could not read ../oiio-images/jpeg2000/broken/issue_3427.jp2:
Failed to decode the codestream in the JP2 file
Invalid image file "../oiio-images/jpeg2000/broken/issue_3427.jp2": Tile part length size inconsistent with stream length
Failed to decode the codestream in the JP2 file
32 x 32, 1 channel, float jpeg2000
Stats Min: 128 (of 255)
Stats Max: 128 (of 255)
Stats Avg: 128.00 (of 255)
Stats StdDev: 0.00 (of 255)
Stats NanCount: 0
Stats InfCount: 0
Stats FiniteCount: 1024
Constant: Yes
Constant Color: 128.00 (of 255)
Monochrome: Yes
ICCProfile of tahoe-icc.jp2
ICCProfile: 0, 0, 2, 48, 65, 68, 66, 69, 2, 16, 0, 0, 109, 110, 116, 114, ... [560 x uint8]
ICCProfile:attributes: "Reflective, Glossy, Positive, Color"
Expand Down
11 changes: 11 additions & 0 deletions testsuite/jpeg2000/ref/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,14 @@ idiff ERROR: Could not read ../oiio-images/jpeg2000/broken/issue_3427.jp2:
Failed to decode the codestream in the JP2 file
Invalid image file "../oiio-images/jpeg2000/broken/issue_3427.jp2": Tile part length size inconsistent with stream length
Failed to decode the codestream in the JP2 file
32 x 32, 1 channel, float jpeg2000
Stats Min: 128 (of 255)
Stats Max: 128 (of 255)
Stats Avg: 128.00 (of 255)
Stats StdDev: 0.00 (of 255)
Stats NanCount: 0
Stats InfCount: 0
Stats FiniteCount: 1024
Constant: Yes
Constant Color: 128.00 (of 255)
Monochrome: Yes
3 changes: 3 additions & 0 deletions testsuite/jpeg2000/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
for f in files:
command += rw_command (imagedir, f, printinfo=False)

# sYCC colr box on a 1-channel image must not run the 3-channel conversion
command += oiiotool("src/sycc-1chan.jp2 --printstats")

# Try to parse library_list to find the OpenJPEG version. If found, return it as
# a single number, e.g. 2.5.3 -> 20503. Return -1 if not found.
def get_openjp_ver():
Expand Down
Binary file added testsuite/jpeg2000/src/sycc-1chan.jp2
Binary file not shown.
Loading