Skip to content

Bug Report: invalid decoded-resolution state causes READ SEGV in opj_tcd_get_decoded_tile_size() #1631

Description

@5asever40-a11y

Summary

A malformed JPEG 2000 codestream can reproducibly crash the OpenJPEG tile header decoding path under AddressSanitizer.

The crash occurs as a READ SEGV in:

opj_tcd_get_decoded_tile_size()
src/lib/openjp2/tcd.c:1426

I am not making a strong claim about code execution. At minimum, this appears to be a reproducible decoder crash / robustness issue caused by invalid decoded-resolution state handling.

Reproducer

Attached archive:

openjpeg_tcd1426.zip

The archive contains:

OpenJPEG_tcd1426_invalid_resolution_state_report_en.md
openjpeg_tcd1426_crash_input.j2k
openjpeg_tcd1426_asan.log

Crash input metadata:

Filename: openjpeg_tcd1426_crash_input.j2k
SHA-256: 660ce4995e12714ba9ed3b19b310fd3d52a5bff27e42a3bf3690bba976083f2e
Size: 5476 bytes

Reproduction Command

BIN=/home/js/ctf_dreamhack/fuzzing/2_fuzz/openjpeg/campaign/bin/tile_header_data_partial_afl_asan
CRASH="/path/to/openjpeg_tcd1426_crash_input.j2k"

ASAN_OPTIONS=detect_leaks=0:symbolize=1:handle_segv=1:abort_on_error=0 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
"$BIN" "$CRASH" 2>&1 | tee /tmp/openjpeg_tcd1426_asan.log

ASAN Output

AddressSanitizer:DEADLYSIGNAL
=================================================================
ERROR: AddressSanitizer: SEGV on unknown address 0x615fffffffc8
The signal is caused by a READ memory access.
    #0 opj_tcd_get_decoded_tile_size
       src/lib/openjp2/tcd.c:1426:37
    #1 opj_j2k_read_tile_header
       src/lib/openjp2/j2k.c:10097:24
    #2 opj_read_tile_header
       src/lib/openjp2/openjpeg.c:575:16
    #3 LLVMFuzzerTestOneInput
       campaign/harnesses/tile_header_data_partial_fuzzer.cpp:344:10
    #4 main
       campaign/harnesses/tile_header_data_partial_afl_driver.cpp:50:5

SUMMARY: AddressSanitizer: SEGV
src/lib/openjp2/tcd.c:1426:37
in opj_tcd_get_decoded_tile_size

Root Cause Summary

The crash appears to be related to opj_j2k_set_decoded_resolution_factor() updating the decoder state's m_reduce field before validating whether the requested resolution factor is legal.

The reproducer contains a COD marker at offset 136.

COD payload:
00000001000204040001

byte_at_145              = 0x02
num decomposition raw    = 2
OpenJPEG numresolutions  = raw + 1 = 3
res_factor               = 3

If the caller ignores the failure return value from opj_set_decoded_resolution_factor() and continues decoding, opj_tcd_init_tile() can later compute:

minimum_num_resolutions = 3 - 3 = 0

This is consistent with opj_tcd_get_decoded_tile_size() deriving a resolutions - 1 pointer and crashing on a read.

Suggested Fix / Hardening Direction

Validate res_factor before mutating decoder state.

Current order:

p_j2k->m_cp.m_specific_param.m_dec.m_reduce = res_factor;
if (res_factor >= max_res) {
    return OPJ_FALSE;
}

Suggested order:

if (res_factor >= max_res) {
    return OPJ_FALSE;
}
p_j2k->m_cp.m_specific_param.m_dec.m_reduce = res_factor;

Additional hardening ideas:

  • Validate minimum_num_resolutions before using it to derive a pointer into resolutions.
  • Treat a failed opj_set_decoded_resolution_factor() as fatal in callers, or restore the previous state on failure.
  • Add a regression test using the attached reproducer.

Reporter Attribution

  • OH HAN GUEL
  • KANG DAEUN

Thank you for taking the time to review this report.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions