CVE-2026-2648
Overview
Files Changed
third_party/libopenjpeg/0047-opj_j2k_read_sod.patchthird_party/libopenjpeg/README.pdfiumthird_party/libopenjpeg/j2k.c
Patch
From 40e5a93503adcdd4b9b3fdc085a31a58b7b0f2dd Mon Sep 17 00:00:00 2001 From: Tom Sepez <[email protected]> Date: Wed, 04 Feb 2026 10:59:25 -0800 Subject: [PATCH] Fix indexing in opj_j2k_read_sod() Apply same check as in opj_j2k_add_tlmarker(). Bug: 477033835 Change-Id: I18def8cdd683cb1e8ed9359914e286de9837d382 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/142390 Auto-Submit: Tom Sepez <[email protected]> Reviewed-by: Lei Zhang <[email protected]> Commit-Queue: Lei Zhang <[email protected]> Commit-Queue: Tom Sepez <[email protected]> --- diff --git a/third_party/libopenjpeg/0047-opj_j2k_read_sod.patch b/third_party/libopenjpeg/0047-opj_j2k_read_sod.patch new file mode 100644 index 0000000..93259a5 --- /dev/null +++ b/third_party/libopenjpeg/0047-opj_j2k_read_sod.patch @@ -0,0 +1,27 @@ +diff --git a/third_party/libopenjpeg/j2k.c b/third_party/libopenjpeg/j2k.c +index 5e9d75076..fc5ce62df 100644 +--- a/third_party/libopenjpeg/j2k.c ++++ b/third_party/libopenjpeg/j2k.c +@@ -5082,13 +5082,15 @@ static OPJ_BOOL opj_j2k_read_sod(opj_j2k_t *p_j2k, + + OPJ_UINT32 l_current_tile_part = + l_cstr_index->tile_index[p_j2k->m_current_tile_number].current_tpsno; +- l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index[l_current_tile_part].end_header +- = +- l_current_pos; +- l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index[l_current_tile_part].end_pos +- = +- l_current_pos + p_j2k->m_specific_param.m_decoder.m_sot_length + 2; +- ++ if (l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index && ++ l_current_tile_part < l_cstr_index->tile_index[p_j2k->m_current_tile_number].nb_tps) { ++ l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index[l_current_tile_part].end_header ++ = ++ l_current_pos; ++ l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index[l_current_tile_part].end_pos ++ = ++ l_current_pos + p_j2k->m_specific_param.m_decoder.m_sot_length + 2; ++ } + if (OPJ_FALSE == opj_j2k_add_tlmarker(p_j2k->m_current_tile_number, + l_cstr_index, + J2K_MS_SOD, diff --git a/third_party/libopenjpeg/README.pdfium b/third_party/libopenjpeg/README.pdfium index 289f19a..905c640 100644 --- a/third_party/libopenjpeg/README.pdfium +++ b/third_party/libopenjpeg/README.pdfium @@ -33,6 +33,7 @@ 0039-opj_mqc_renorme.patch: Remove unused opj_mqc_renorme(). 0041-remove_opj_clock.patch: Remove unused opj_clock.h include. 0046-func-ptr-mixup.patch: Prevent mixing up function pointer types. +0047-opj_j2k_read_sod.patch: Fix out of bounds read. Note: diff --git a/third_party/libopenjpeg/j2k.c b/third_party/libopenjpeg/j2k.c index 5e9d750..fc5ce62 100644 --- a/third_party/libopenjpeg/j2k.c +++ b/third_party/libopenjpeg/j2k.c @@ -5082,13 +5082,15 @@ OPJ_UINT32 l_current_tile_part = l_cstr_index->tile_index[p_j2k->m_current_tile_number].current_tpsno; - l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index[l_current_tile_part].end_header - = - l_current_pos; - l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index[l_current_tile_part].end_pos - = - l_current_pos + p_j2k->m_specific_param.m_decoder.m_sot_length + 2; - + if (l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index && + l_current_tile_part < l_cstr_index->tile_index[p_j2k->m_current_tile_number].nb_tps) { + l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index[l_current_tile_part].end_header + = + l_current_pos; + l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index[l_current_tile_part].end_pos + = + l_current_pos + p_j2k->m_specific_param.m_decoder.m_sot_length + 2; + } if (OPJ_FALSE == opj_j2k_add_tlmarker(p_j2k->m_current_tile_number, l_cstr_index, J2K_MS_SOD,
Original Bug Report
PDFium heap-buffer-overflow at opj_j2k_read_sod
VULNERABILITY DETAILS
A heap buffer overflow vulnerability exists in OpenJPEG’s TLM (Tile Length Marker) decoding functionality. The vulnerability allows an attacker to write out-of-bounds to heap memory when processing a specially crafted JPEG 2000 file. The root cause is a missing bounds check in opj_j2k_read_sod() where the current_tpsno (current tile-part number) value derived from SOT markers is used to index the tp_index array without validating it against the allocated size (nb_tps).
REPRODUCTION CASE
Attached testcase uses multiple tiles, and more SOT fields, to crash even without ASAN. Pdfium_test ASAN log:
==48964==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x1235a21aa800 at pc 0x7ff74b7de0a0 bp 0x0057daefe200 sp 0x0057daefe248
WRITE of size 8 at 0x1235a21aa800 thread T0
#0 0x7ff74b7de09f in opj_j2k_read_sod C:\b\s\w\ir\cache\builder\src\third_party\pdfium\third_party\libopenjpeg\j2k.c:5086
#1 0x7ff74b7de09f in opj_j2k_read_tile_header C:\b\s\w\ir\cache\builder\src\third_party\pdfium\third_party\libopenjpeg\j2k.c:9923:19
#2 0x7ff74b7f61f7 in opj_j2k_decode_tiles C:\b\s\w\ir\cache\builder\src\third_party\pdfium\third_party\libopenjpeg\j2k.c:12108:19
#3 0x7ff74b7e4d56 in opj_j2k_exec C:\b\s\w\ir\cache\builder\src\third_party\pdfium\third_party\libopenjpeg\j2k.c:9190
#4 0x7ff74b7e4d56 in opj_j2k_decode C:\b\s\w\ir\cache\builder\src\third_party\pdfium\third_party\libopenjpeg\j2k.c:12455:11
#5 0x7ff74b70f36e in fxcodec::CJPX_Decoder::StartDecode(void) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fxcodec\jpx\cjpx_decoder.cpp:519:11
#6 0x7ff74b934e72 in CPDF_DIB::LoadJpxBitmap(unsigned char) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\page\cpdf_dib.cpp:597:17
#7 0x7ff74b92e7e2 in CPDF_DIB::CreateDecoder(unsigned char) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\page\cpdf_dib.cpp:452:22
#8 0x7ff74b9310f6 in CPDF_DIB::StartLoadDIBBase(bool, class CPDF_Dictionary const *, class CPDF_Dictionary const *, bool, enum CPDF_ColorSpace::Family, bool, class CFX_STemplate<int> const &) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\page\cpdf_dib.cpp:207:31
#9 0x7ff74b966e48 in CPDF_PageImageCache::Entry::StartGetCachedBitmap(class CPDF_PageImageCache *, class CPDF_Dictionary const *, class CPDF_Dictionary const *, bool, enum CPDF_ColorSpace::Family, bool, class CFX_STemplate<int> const &) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\page\cpdf_pageimagecache.cpp:283:60
#10 0x7ff74b966690 in CPDF_PageImageCache::StartGetCachedBitmap(class fxcrt::RetainPtr<class CPDF_Image>, class CPDF_Dictionary const *, class CPDF_Dictionary const *, bool, enum CPDF_ColorSpace::Family, bool, class CFX_STemplate<int> const &) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\page\cpdf_pageimagecache.cpp:191:53
#11 0x7ff74b9574f8 in CPDF_ImageLoader::Start(class CPDF_ImageObject const *, class CPDF_PageImageCache *, class CPDF_Dictionary const *, class CPDF_Dictionary const *, bool, enum CPDF_ColorSpace::Family, bool, class CFX_STemplate<int> const &) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\page\cpdf_imageloader.cpp:35:31
#12 0x7ff74b9b270e in CPDF_ImageRenderer::StartLoadDIBBase(void) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\render\cpdf_imagerenderer.cpp:73:17
#13 0x7ff74b9b77d0 in CPDF_ImageRenderer::Start(class CPDF_ImageObject *, class CFX_Matrix const &, bool) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\render\cpdf_imagerenderer.cpp:202:7
#14 0x7ff74b9d09d1 in CPDF_RenderStatus::ContinueSingleObject(class CPDF_PageObject *, class CFX_Matrix const &, class PauseIndicatorIface *) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\render\cpdf_renderstatus.cpp:286:25
#15 0x7ff74b9bc09a in CPDF_ProgressiveRenderer::Continue(class PauseIndicatorIface *) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\render\cpdf_progressiverenderer.cpp:95:29
#16 0x7ff74ad0ae5c in `anonymous namespace'::RenderPageImpl C:\b\s\w\ir\cache\builder\src\third_party\pdfium\fpdfsdk\cpdfsdk_renderpage.cpp:87:23
#17 0x7ff74ad0b22d in CPDFSDK_RenderPageWithContext(class CPDF_PageRenderContext *, class CPDF_Page *, int, int, int, int, int, int, struct FPDF_COLORSCHEME_ const *, bool, class CPDFSDK_PauseAdapter *) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\fpdfsdk\cpdfsdk_renderpage.cpp:117:3
#18 0x7ff74ad23008 in FPDF_RenderPageBitmapWithColorScheme_Start C:\b\s\w\ir\cache\builder\src\third_party\pdfium\fpdfsdk\fpdf_progressive.cpp:83:3
#19 0x7ff74acae42a in `anonymous namespace'::ProgressiveBitmapPageRenderer::Start C:\b\s\w\ir\cache\builder\src\third_party\pdfium\testing\pdfium_test.cc:1137:9
#20 0x7ff74aca5254 in `anonymous namespace'::PdfProcessor::ProcessPage C:\b\s\w\ir\cache\builder\src\third_party\pdfium\testing\pdfium_test.cc:1607
#21 0x7ff74aca5254 in `anonymous namespace'::Processor::ProcessPdf C:\b\s\w\ir\cache\builder\src\third_party\pdfium\testing\pdfium_test.cc:1789
#22 0x7ff74aca5254 in main C:\b\s\w\ir\cache\builder\src\third_party\pdfium\testing\pdfium_test.cc:2117:17
#23 0x7ff750dcb22f in invoke_main D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
#24 0x7ff750dcb22f in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
#25 0x7ff99d2ce8d6 (C:\WINDOWS\System32\KERNEL32.DLL+0x18002e8d6)
#26 0x7ff99dd6c53b (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18008c53b)
0x1235a21aa800 is located 8 bytes after 24-byte region [0x1235a21aa7e0,0x1235a21aa7f8)
allocated by thread T0 here:
#0 0x7ff8e583ca26 (F:\fuzz\chromium-146.0.7637.0-win64-asan\clang_rt.asan_dynamic-x86_64.dll+0x18004ca26)
#1 0x7ff7509f494d in calloc C:\b\s\w\ir\cache\builder\src\third_party\llvm\compiler-rt\lib\asan\asan_malloc_win_thunk.cpp:82
#2 0x7ff74b7e9a35 in opj_j2k_build_tp_index_from_tlm C:\b\s\w\ir\cache\builder\src\third_party\pdfium\third_party\libopenjpeg\j2k.c:8948:57
#3 0x7ff74b7e7b33 in opj_j2k_read_header_procedure C:\b\s\w\ir\cache\builder\src\third_party\pdfium\third_party\libopenjpeg\j2k.c:9161:5
#4 0x7ff74b7da366 in opj_j2k_exec C:\b\s\w\ir\cache\builder\src\third_party\pdfium\third_party\libopenjpeg\j2k.c:9190
#5 0x7ff74b7da366 in opj_j2k_read_header C:\b\s\w\ir\cache\builder\src\third_party\pdfium\third_party\libopenjpeg\j2k.c:8535:11
#6 0x7ff74b70c620 in fxcodec::CJPX_Decoder::Init(class pdfium::span<unsigned char const, -1, unsigned char const *>, unsigned char, bool) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fxcodec\jpx\cjpx_decoder.cpp:503:8
#7 0x7ff74b70c002 in fxcodec::CJPX_Decoder::Create(class pdfium::span<unsigned char const, -1, unsigned char const *>, enum fxcodec::CJPX_Decoder::ColorSpaceOption, unsigned char, bool) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fxcodec\jpx\cjpx_decoder.cpp:438:17
#8 0x7ff74b934e1b in CPDF_DIB::LoadJpxBitmap(unsigned char) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\page\cpdf_dib.cpp:587:7
#9 0x7ff74b92e7e2 in CPDF_DIB::CreateDecoder(unsigned char) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\page\cpdf_dib.cpp:452:22
#10 0x7ff74b9310f6 in CPDF_DIB::StartLoadDIBBase(bool, class CPDF_Dictionary const *, class CPDF_Dictionary const *, bool, enum CPDF_ColorSpace::Family, bool, class CFX_STemplate<int> const &) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\page\cpdf_dib.cpp:207:31
#11 0x7ff74b966e48 in CPDF_PageImageCache::Entry::StartGetCachedBitmap(class CPDF_PageImageCache *, class CPDF_Dictionary const *, class CPDF_Dictionary const *, bool, enum CPDF_ColorSpace::Family, bool, class CFX_STemplate<int> const &) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\page\cpdf_pageimagecache.cpp:283:60
#12 0x7ff74b966690 in CPDF_PageImageCache::StartGetCachedBitmap(class fxcrt::RetainPtr<class CPDF_Image>, class CPDF_Dictionary const *, class CPDF_Dictionary const *, bool, enum CPDF_ColorSpace::Family, bool, class CFX_STemplate<int> const &) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\page\cpdf_pageimagecache.cpp:191:53
#13 0x7ff74b9574f8 in CPDF_ImageLoader::Start(class CPDF_ImageObject const *, class CPDF_PageImageCache *, class CPDF_Dictionary const *, class CPDF_Dictionary const *, bool, enum CPDF_ColorSpace::Family, bool, class CFX_STemplate<int> const &) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\page\cpdf_imageloader.cpp:35:31
#14 0x7ff74b9b270e in CPDF_ImageRenderer::StartLoadDIBBase(void) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\render\cpdf_imagerenderer.cpp:73:17
#15 0x7ff74b9b77d0 in CPDF_ImageRenderer::Start(class CPDF_ImageObject *, class CFX_Matrix const &, bool) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\render\cpdf_imagerenderer.cpp:202:7
#16 0x7ff74b9d09d1 in CPDF_RenderStatus::ContinueSingleObject(class CPDF_PageObject *, class CFX_Matrix const &, class PauseIndicatorIface *) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\render\cpdf_renderstatus.cpp:286:25
#17 0x7ff74b9bc09a in CPDF_ProgressiveRenderer::Continue(class PauseIndicatorIface *) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fpdfapi\render\cpdf_progressiverenderer.cpp:95:29
#18 0x7ff74ad0ae5c in `anonymous namespace'::RenderPageImpl C:\b\s\w\ir\cache\builder\src\third_party\pdfium\fpdfsdk\cpdfsdk_renderpage.cpp:87:23
#19 0x7ff74ad0b22d in CPDFSDK_RenderPageWithContext(class CPDF_PageRenderContext *, class CPDF_Page *, int, int, int, int, int, int, struct FPDF_COLORSCHEME_ const *, bool, class CPDFSDK_PauseAdapter *) C:\b\s\w\ir\cache\builder\src\third_party\pdfium\fpdfsdk\cpdfsdk_renderpage.cpp:117:3
#20 0x7ff74ad23008 in FPDF_RenderPageBitmapWithColorScheme_Start C:\b\s\w\ir\cache\builder\src\third_party\pdfium\fpdfsdk\fpdf_progressive.cpp:83:3
#21 0x7ff74acae42a in `anonymous namespace'::ProgressiveBitmapPageRenderer::Start C:\b\s\w\ir\cache\builder\src\third_party\pdfium\testing\pdfium_test.cc:1137:9
#22 0x7ff74aca5254 in `anonymous namespace'::PdfProcessor::ProcessPage C:\b\s\w\ir\cache\builder\src\third_party\pdfium\testing\pdfium_test.cc:1607
#23 0x7ff74aca5254 in `anonymous namespace'::Processor::ProcessPdf C:\b\s\w\ir\cache\builder\src\third_party\pdfium\testing\pdfium_test.cc:1789
#24 0x7ff74aca5254 in main C:\b\s\w\ir\cache\builder\src\third_party\pdfium\testing\pdfium_test.cc:2117:17
#25 0x7ff750dcb22f in invoke_main D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
#26 0x7ff750dcb22f in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
#27 0x7ff99d2ce8d6 (C:\WINDOWS\System32\KERNEL32.DLL+0x18002e8d6)
#28 0x7ff99dd6c53b (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18008c53b)
SUMMARY: AddressSanitizer: heap-buffer-overflow C:\b\s\w\ir\cache\builder\src\third_party\pdfium\third_party\libopenjpeg\j2k.c:5086 in opj_j2k_read_sod
Shadow bytes around the buggy address:
0x1235a21aa580: fd fd fa fa fd fd fd fd fa fa fd fd fd fd fa fa
0x1235a21aa600: fd fd fd fd fa fa 00 00 00 00 fa fa 00 00 00 00
0x1235a21aa680: fa fa fd fd fd fd fa fa fd fd fd fd fa fa fd fd
0x1235a21aa700: fd fd fa fa 00 00 00 00 fa fa fd fd fd fa fa fa
0x1235a21aa780: 00 00 00 00 fa fa 00 00 00 fa fa fa 00 00 00 fa
=>0x1235a21aa800:[fa]fa 00 00 00 fa fa fa fa fa fa fa fa fa fa fa
0x1235a21aa880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1235a21aa900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1235a21aa980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1235a21aaa00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1235a21aaa80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Root Cause Analysis
Overview
The vulnerability stems from a mismatch between:
- The
tp_indexarray size, which is allocated based on TLM (Tile Length Marker) entries - The
current_tpsnovalue, which is set from SOT (Start Of Tile-part) marker’sTPsotfield
When a maliciously crafted file provides inconsistent TLM and SOT data, the decoder can write beyond the allocated tp_index buffer.
Step 1: TLM Allocates tp_index Based on Tile-Part Count
In j2k.c opj_j2k_build_tp_index_from_tlm() (lines 8914-8946), the tp_index array is allocated based on the number of tile-part entries found in TLM markers:
// Lines 8914-8919: First pass counts tile-parts per tile
for (i = 0; i < l_tlm->m_entries_count; ++i) {
OPJ_UINT32 l_tile_index_no = l_tlm->m_tile_part_infos[i].m_tile_index;
assert(l_tile_index_no < p_j2k->cstr_index->nb_of_tiles);
p_j2k->cstr_index->tile_index[l_tile_index_no].tileno = l_tile_index_no;
++p_j2k->cstr_index->tile_index[l_tile_index_no].current_nb_tps;
}
// Lines 8938-8946: Allocate tp_index based on count
if (!l_tile_index->tp_index) {
l_tile_index->tp_index = (opj_tp_index_t *) opj_calloc(
l_tile_index->current_nb_tps, sizeof(opj_tp_index_t));
if (! l_tile_index->tp_index) {
opj_event_msg(p_manager, EVT_ERROR,
"opj_j2k_build_tp_index_from_tlm(): tile index allocation failed\n");
goto error;
}
}
If TLM says tile 0 has 2 tile-parts, then tp_index[2] is allocated with valid indices 0 and 1.
Step 2: SOT Sets current_tpsno Without Sufficient Validation
In opj_j2k_read_sot() (lines 4659-4660), the current_tpsno is set directly from the SOT marker’s TPsot field:
// Lines 4659-4660: current_tpsno set UNCONDITIONALLY from SOT
p_j2k->cstr_index->tile_index[p_j2k->m_current_tile_number].current_tpsno =
l_current_part;
Step 3: Inadequate TLM Invalidation Check
The check that should invalidate TLM when SOT data conflicts (lines 4662-4668) only compares TNsot (total number of tile-parts) against TLM data, NOT TPsot (current tile-part number):
// Lines 4662-4668: Only checks TNsot, NOT TPsot
if (!p_j2k->m_specific_param.m_decoder.m_tlm.m_is_invalid &&
l_num_parts > // ← l_num_parts is TNsot
p_j2k->cstr_index->tile_index[p_j2k->m_current_tile_number].nb_tps) {
opj_event_msg(p_manager, EVT_WARNING,
"SOT marker for tile %u declares more tile-parts than found in TLM marker.",
p_j2k->m_current_tile_number);
p_j2k->m_specific_param.m_decoder.m_tlm.m_is_invalid = OPJ_TRUE;
}
When TNsot = 0 (unknown), this check passes even if TPsot >= nb_tps.
Step 4: Vulnerable Access in opj_j2k_read_sod
In opj_j2k_read_sod() (lines 5083-5090), the current_tpsno value is used to index tp_index without any bounds check:
// Lines 5083-5090: VULNERABLE - No bounds check before array access
OPJ_UINT32 l_current_tile_part =
l_cstr_index->tile_index[p_j2k->m_current_tile_number].current_tpsno;
l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index[l_current_tile_part].end_header
=
l_current_pos;
l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index[l_current_tile_part].end_pos
=
l_current_pos + p_j2k->m_specific_param.m_decoder.m_sot_length + 2;
Compare this to the protected code in opj_j2k_add_tlmarker() (lines 8462-8465):
// Lines 8462-8465: PROTECTED - Has bounds check
if (cstr_index->tile_index[tileno].tp_index &&
l_current_tile_part < cstr_index->tile_index[tileno].nb_tps) { // ← Bounds check
cstr_index->tile_index[tileno].tp_index[l_current_tile_part].start_pos = pos;
}
Memory Corruption Details
The overflow writes OPJ_OFF_T values to heap memory:
typedef struct opj_tp_index {
OPJ_OFF_T start_pos; // 8 bytes
OPJ_OFF_T end_header; // 8 bytes - WRITTEN BY VULNERABLE CODE
OPJ_OFF_T end_pos; // 8 bytes - WRITTEN BY VULNERABLE CODE
} opj_tp_index_t; // Total: 24 bytes per entry
Attacker-controlled values:
end_header= current stream position (partially controlled via file layout)end_pos= current stream position + SOT length (attacker-controlled)
Fix : Add Bounds Check in opj_j2k_read_sod
diff --git a/src/lib/openjp2/j2k.c b/src/lib/openjp2/j2k.c
index a2014c8..c4304c7 100644
--- a/src/lib/openjp2/j2k.c
+++ b/src/lib/openjp2/j2k.c
@@ -5082,12 +5082,15 @@ static OPJ_BOOL opj_j2k_read_sod(opj_j2k_t *p_j2k,
OPJ_UINT32 l_current_tile_part =
l_cstr_index->tile_index[p_j2k->m_current_tile_number].current_tpsno;
+ if (l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index &&
+ l_current_tile_part < l_cstr_index->tile_index[p_j2k->m_current_tile_number].nb_tps) {
l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index[l_current_tile_part].end_header
=
l_current_pos;
l_cstr_index->tile_index[p_j2k->m_current_tile_number].tp_index[l_current_tile_part].end_pos
=
l_current_pos + p_j2k->m_specific_param.m_decoder.m_sot_length + 2;
+ }
if (OPJ_FALSE == opj_j2k_add_tlmarker(p_j2k->m_current_tile_number,
l_cstr_index,
CREDIT INFORMATION
Reporter credit: soiax