opj_tcd: fix out-of-bounds read in opj_tcd_get_decoded_tile_size() when the reduce factor equals numresolutions (#1631)#1640
Open
Alearner12 wants to merge 1 commit into
Conversation
…en the reduce factor equals numresolutions (uclouvain#1631) This patch addresses an OOB read triggered when minimum_num_resolutions underflows to 0. It fixes the setter opj_j2k_set_decoded_resolution_factor() to validate against all components before mutating decoder state, and updates the boundary check in opj_tcd_init_tile() to use <= to ensure clamping.
|
Hello,
Thank you for reproducing the issue and preparing the fix in PR #1640.
I saw that the PR describes the bug as an out-of-bounds read in
opj_tcd_get_decoded_tile_size() when the reduce factor equals
numresolutions,
and that the reachability is limited to an application/API path where
opj_set_decoded_resolution_factor() returns OPJ_FALSE but the caller
continues
to opj_read_tile_header().
I understand that this is low severity and not reachable through the normal
opj_decompress path. Still, since the issue affects a public API path,
causes
a reproducible ASan READ SEGV, and has a concrete fix, I wanted to ask:
Do the OpenJPEG maintainers plan to request or assign a CVE ID, or publish a
GitHub Security Advisory for this issue?
If the project does not plan to do so, would it be acceptable for us as the
reporters to request a CVE ID while clearly stating the limited reachability
and low severity?
We will make sure not to overstate the impact. The request would describe it
as a limited-reachability out-of-bounds read / denial-of-service condition,
not as code execution.
Thank you again for reviewing and fixing the report.
Best regards,
OH HAN GUEL
KANG DAEUN
2026년 5월 31일 (일) 오전 2:40, Sidhartha kumar ***@***.***>님이 작성:
… PR: Fix OOB read in opj_tcd_get_decoded_tile_size() when reduce factor ==
numresolutions
*Base:* 21b70b0 (v2.5.4-23) · *Branch:* fix-1631-resolution-factor-oob ·
*Diff:* j2k.c +12/-2, tcd.c +7/-1
*Closes:* #1631 <#1631>
(reported by @5asever40-a11y <https://github.com/5asever40-a11y>,
crediting OH HAN GUEL and KANG DAEUN)
------------------------------
Bug
opj_tcd_get_decoded_tile_size() (tcd.c:1421) indexes
l_tile_comp->resolutions + l_tile_comp->minimum_num_resolutions - 1.
When minimum_num_resolutions == 0 this is resolutions[-1], an
out-of-bounds
read dereferenced at tcd.c:1426 the SEGV reported in #1631
<#1631> (ASan, READ on an
unmapped address).
minimum_num_resolutions is computed in opj_tcd_init_tile() (tcd.c:880):
if (l_tccp->numresolutions < l_cp->m_specific_param.m_dec.m_reduce) {
l_tilec->minimum_num_resolutions = 1;
} else {
l_tilec->minimum_num_resolutions =
l_tccp->numresolutions - l_cp->m_specific_param.m_dec.m_reduce; // 0 when equal
}
The intended invariant is m_reduce < numresolutions — COD parsing
enforces it
in opj_j2k_read_SPCod_SPCoc() (j2k.c:10979) by rejecting
m_reduce >= numresolutions. But this guard uses <, so when
m_reduce == numresolutions it falls into the else branch and yields 0.
m_reduce reaches that equal-to value through
opj_j2k_set_decoded_resolution_factor() (j2k.c:12556), which writes
m_reduce = res_factor *before* validating and then returns OPJ_FALSE:
p_j2k->m_cp.m_specific_param.m_dec.m_reduce = res_factor; /* mutated first */
...
if (res_factor >= max_res) { ...; return OPJ_FALSE; } /* rejected, but state already changed */
So a *rejected* set_decoded_resolution_factor() leaves the decoder in a
corrupted state, and a later opj_read_tile_header() trips the OOB.
Reachability (honest scoping)
This is *not* reachable through opj_decompress or the in-tree OSS-Fuzz
harnesses (opj_decompress_fuzzer_{J2K,JP2}): those take the full-decode
path,
where COD parsing rejects m_reduce >= numresolutions up front
(opj_decompress -r 3 on a 3-resolution image exits cleanly:
*"The number of resolutions to remove (3) is greater or equal than the
number of resolutions of this component (3)"*). It is reachable from
application code that
calls opj_set_decoded_resolution_factor() with an out-of-range factor and
then
proceeds to opj_read_tile_header() without checking the OPJ_FALSE return.
Severity is correspondingly low the standard decode path is unaffected —
but a
public setter should not corrupt decoder state on its rejection path, and
the
tcd.c guard is internally inconsistent with the COD-parse invariant.
Fix
Two small, independent changes (either alone prevents the crash; together
they
fix the root cause and harden the point of use):
1.
*opj_j2k_set_decoded_resolution_factor() — root cause.* Validate every
component before mutating any state, so a rejected factor is a no-op
and
leaves m_reduce and the per-component factor values unchanged. (This
also
closes a pre-existing partial-state issue: comps[i].factor was being
set
incrementally before a *later* component could fail validation.)
2.
*opj_tcd_init_tile() — defense in depth.* Use <= so
minimum_num_resolutions is clamped to >= 1 whenever
m_reduce >= numresolutions, matching the >= invariant the COD parser
already enforces. This guarantees opj_tcd_get_decoded_tile_size() can
never
read resolutions[-1], regardless of how m_reduce was set.
Verification
Built with -fsanitize=address at 21b70b0. A harness reproducing the #1631
<#1631>
sequence (read_header → set_decoded_resolution_factor(rf) →
read_tile_header) on the reported crash input:
reduce factor before after
1, 2 (valid) ok ok — identical tile sizes (13924 / 3600)
*3 (== numresolutions)* *SEGV @ tcd.c:1426* *no crash, clean read*
4 (> numresolutions) ok (already clamped) ok
- *Each fix independently stops the SEGV.* With *only* the tcd.c clamp
(setter
left buggy, so m_reduce is still corrupted to 3), rf=3 no longer
crashes
it clamps to minimum_num_resolutions = 1. With the setter fix, m_reduce
is
never corrupted in the first place.
- *No regression on the normal path:* opj_decompress -r 0 / -r 2
decode the
file unchanged; opj_decompress -r 3 still exits cleanly via the
COD-parse
check. Valid reduce factors (m_reduce < numresolutions) are
byte-for-byte
unaffected — the tcd.c change only diverges from current behavior in
the
m_reduce == numresolutions case, which valid decoding never reaches.
------------------------------
You can view, comment on, or merge this pull request online at:
#1640
Commit Summary
- 404e885
<404e885>
opj_tcd: fix out-of-bounds read in opj_tcd_get_decoded_tile_size() when the
reduce factor equals numresolutions (#1631)
File Changes
(2 files <https://github.com/uclouvain/openjpeg/pull/1640/files>)
- *M* src/lib/openjp2/j2k.c
<https://github.com/uclouvain/openjpeg/pull/1640/files#diff-aa569d1fec07851a18fc1d90ac382f3e83feb4603bd18cf9637e9882864e4ff5>
(8)
- *M* src/lib/openjp2/tcd.c
<https://github.com/uclouvain/openjpeg/pull/1640/files#diff-9f3b24ffd188ebff3aa6707064d92f37fd858a3b78d3984272a7896e6beff360>
(3)
Patch Links:
- https://github.com/uclouvain/openjpeg/pull/1640.patch
- https://github.com/uclouvain/openjpeg/pull/1640.diff
—
Reply to this email directly, view it on GitHub
<#1640?email_source=notifications&email_token=BZ5ELIHZ376MVCUSRTQLL7T45MMH7A5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF4ZTONZUGUYDONZZHGTHEZLBONXW5J3NMVXHI2LPN2SWK5TFNZ2KYZTPN52GK4S7MNWGSY3L>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/BZ5ELIGIJ3BARRWXLJARX7L45MMH7AVCNFSM6AAAAACZTZ6CA6VHI2DSMVQWIX3LMV43ASLTON2WKOZUGU2TKMJWHA3DAMI>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/BZ5ELIAEAZICGEAWF2CR7IL45MMH7A5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF4ZTONZUGUYDONZZHGTHEZLBONXW5J3NMVXHI2LPN2SWK5TFNZ2KUZTPN52GK4S7NFXXG>
and Android
<https://github.com/notifications/mobile/android/BZ5ELICFXQTAJQ2SX6FE6ST45MMH7A5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF4ZTONZUGUYDONZZHGTHEZLBONXW5J3NMVXHI2LPN2SWK5TFNZ2K4ZTPN52GK4S7MFXGI4TPNFSA>.
Download it today!
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR: Fix OOB read in opj_tcd_get_decoded_tile_size() when reduce factor == numresolutions
Base:
21b70b0(v2.5.4-23) · Branch:fix-1631-resolution-factor-oob· Diff: j2k.c +12/-2, tcd.c +7/-1Closes: #1631 (reported by @5asever40-a11y, crediting OH HAN GUEL and KANG DAEUN)
Bug
opj_tcd_get_decoded_tile_size()(tcd.c:1421) indexesl_tile_comp->resolutions + l_tile_comp->minimum_num_resolutions - 1.When
minimum_num_resolutions == 0this isresolutions[-1], an out-of-boundsread dereferenced at tcd.c:1426 the SEGV reported in #1631 (ASan, READ on an
unmapped address).
minimum_num_resolutionsis computed inopj_tcd_init_tile()(tcd.c:880):The intended invariant is
m_reduce < numresolutions— COD parsing enforces itin
opj_j2k_read_SPCod_SPCoc()(j2k.c:10979) by rejectingm_reduce >= numresolutions. But this guard uses<, so whenm_reduce == numresolutionsit falls into theelsebranch and yields0.m_reducereaches that equal-to value throughopj_j2k_set_decoded_resolution_factor()(j2k.c:12556), which writesm_reduce = res_factorbefore validating and then returnsOPJ_FALSE:So a rejected
set_decoded_resolution_factor()leaves the decoder in acorrupted state, and a later
opj_read_tile_header()trips the OOB.Reachability (honest scoping)
This is not reachable through
opj_decompressor the in-tree OSS-Fuzzharnesses (
opj_decompress_fuzzer_{J2K,JP2}): those take the full-decode path,where COD parsing rejects
m_reduce >= numresolutionsup front(
opj_decompress -r 3on a 3-resolution image exits cleanly:"The number of resolutions to remove (3) is greater or equal than the number of
resolutions of this component (3)"). It is reachable from application code that
calls
opj_set_decoded_resolution_factor()with an out-of-range factor and thenproceeds to
opj_read_tile_header()without checking theOPJ_FALSEreturn.Severity is correspondingly low the standard decode path is unaffected — but a
public setter should not corrupt decoder state on its rejection path, and the
tcd.c guard is internally inconsistent with the COD-parse invariant.
Fix
Two small, independent changes (either alone prevents the crash; together they
fix the root cause and harden the point of use):
opj_j2k_set_decoded_resolution_factor()— root cause. Validate everycomponent before mutating any state, so a rejected factor is a no-op and
leaves
m_reduceand the per-componentfactorvalues unchanged. (This alsocloses a pre-existing partial-state issue:
comps[i].factorwas being setincrementally before a later component could fail validation.)
opj_tcd_init_tile()— defense in depth. Use<=sominimum_num_resolutionsis clamped to>= 1wheneverm_reduce >= numresolutions, matching the>=invariant the COD parseralready enforces. This guarantees
opj_tcd_get_decoded_tile_size()can neverread
resolutions[-1], regardless of howm_reducewas set.Verification
Built with
-fsanitize=addressat21b70b0. A harness reproducing the #1631sequence (
read_header→set_decoded_resolution_factor(rf)→read_tile_header) on the reported crash input:left buggy, so
m_reduceis still corrupted to 3), rf=3 no longer crashesit clamps to
minimum_num_resolutions = 1. With the setter fix,m_reduceisnever corrupted in the first place.
opj_decompress -r 0/-r 2decode thefile unchanged;
opj_decompress -r 3still exits cleanly via the COD-parsecheck. Valid reduce factors (
m_reduce < numresolutions) are byte-for-byteunaffected — the tcd.c change only diverges from current behavior in the
m_reduce == numresolutionscase, which valid decoding never reaches.