CVE-2026-21353 AI-generated analysis
platform/external/dng_sdk (Adobe DNG SDK 1.7.1): JXL codec glue (dng_jxl.cpp), opcode processing (dng_misc_opcodes.cpp, dng_gain_map.cpp, dng_reference.cpp), memory blocks, safe arithmetic helpers
Updates external/dng_sdk from build 2410 to 2471, adding overflow-checked arithmetic and range checks to the JXL decode/encode pixel-buffer geometry and allocation sizes in dng_jxl.cpp, an overflow check in dng_ref_counted_block::Allocate, an off-by-one fix in dng_opcode_MapTable::ReplicateLastEntry, and division-by-zero guards for blackLevel == 65535 in the GainMap, MapTable, Vignette and BaselineMapPoly paths.
Root cause
In source/dng_jxl.cpp the pixel-buffer geometry used for JXL processing was derived from decoder-supplied values without overflow checks: jxl_image_chunk_reader and jxl_buffer_chunk_reader get_color_channel_data_at cast size_t xpos/ypos/xsize/ysize to int32 and added them with plain arithmetic to form fArea, and computed fRowStep with SafeUint32Mult (the fix switches to SafeInt32Mult with 0x7fffffff bounds, suggesting a signed-range concern); dng_jxl_decoder::Decode assigned basicInfo.xsize/ysize to dng_point fields without a range check, computed fRowStep with plain multiplication, computed bytesNeeded with unchecked uint64 multiplication, and cast the JxlDecoderImageOutBufferSize result to uint32 without a range check; jxl_memory_block added 160u to bytesNeeded without overflow check and did not check the Malloc result; EncodeJXL_Common computed fRowStep and previewBytesNeeded with plain int32/uint32 multiplication. Wrapped strides or sizes are the hazard these checks guard against (the downstream pixel addressing is not shown). Separately, dng_opcode_MapTable::ReplicateLastEntry read table[fCount] where the table has 0x10000 entries and fCount may equal 0x10000 (one past the end, and in any case the wrong entry), and several black-level normalisations divided by (1 - blackLevel/65535), which is zero when blackLevel == 65535.
How the fix works
- source/dng_jxl.cpp:jxl_memory_block::jxl_memory_block: computes mallocSize = 160u + bytesNeeded into a size_t, throws ThrowOverflow if mallocSize <= bytesNeeded, and throws ThrowMemoryFull if allocator.Malloc returns null.
- source/dng_jxl.cpp:jxl_image_chunk_reader get_color_channel_data_at: rejects xpos/ypos/xsize/ysize > 0x7fffffff with ThrowOverflow("coordinate too large"); computes fArea.b/fArea.r with SafeInt32Add; DNG_REQUIREs fPlanes <= 0x7fffffff and fArea.W() <= 0x7fffffff; computes fRowStep with SafeInt32Mult instead of SafeUint32Mult.
- source/dng_jxl.cpp:jxl_buffer_chunk_reader get_color_channel_data_at: same coordinate limit; fArea.t/l/b/r now computed with SafeInt32Add against fBuffer.fArea; same DNG_REQUIREs; fRowStep via SafeInt32Mult. bytesNeeded was already SafeUint32Mult.
- source/dng_jxl.cpp:EncodeJXL_Common: DNG_REQUIRE fArea.W() <= 0x7fffffff; fRowStep via SafeInt32Mult; previewBytesNeeded via 3-arg SafeUint32Mult before host.Allocate.
- source/dng_jxl.cpp:dng_jxl_decoder::Decode: DNG_REQUIRE basicInfo.xsize/ysize <= 0x7fffffff before assigning to dng_point; buffer.fRowStep and wholeBuffer.fRowStep via SafeInt32Mult; bytesNeeded via new 3-arg SafeInt64Mult; DNG_REQUIRE size <= 0xffffffff before the (uint32) cast passed to host.Allocate.
- source/dng_safe_arithmetic.h/.cpp: adds int64 SafeInt64Mult(int64, int64, int64) (chained 2-arg calls) used by Decode; removes the __ANDROID__ special case that forced __USE_BUILTIN_SMULL_OVERFLOW to 0, so Android clang builds use the __builtin_smull_overflow path (SafeInt64MultByClang) instead of SafeInt64MultSlow.
- source/dng_ref_counted_block.cpp:dng_ref_counted_block::Allocate: computes size + sizeof(header) into size_t mallocSize and throws ThrowOverflow if mallocSize <= size before malloc.
- source/dng_misc_opcodes.cpp:dng_opcode_MapTable::ReplicateLastEntry: reads table[fCount - 1] instead of table[fCount]; the constructor already rejects fCount == 0 and fCount > 0x10000, so the index is in range.
- source/dng_misc_opcodes.cpp:dng_opcode_MapTable::Prepare, source/dng_gain_map.cpp:dng_opcode_GainMap::ProcessArea, source/dng_reference.cpp:RefVignette32 and RefBaselineMapPoly32: guard the division so that blackLevel == 65535 yields a scale of 0.0 instead of dividing by zero.
- source/dng_flags.h: build version 2410 -> 2471; source/dng_tag_codes.h: appends tcWebP to the end of the enum (no evident security effect).
Completeness
- All JXL pixel-buffer stride and size computations visible in the diff (two chunk readers, EncodeJXL_Common, both buffers in Decode) now use Safe* arithmetic or explicit upper bounds.
- The chunk-reader checks only prevent integer wrap; whether the resulting fArea is validated against the destination image/buffer bounds is not shown in the material.
- In dng_ref_counted_block::Allocate, size is uint32 and sizeof(header) is size_t, so the new check can only trigger on 32-bit builds; on 64-bit the addition cannot wrap.
- The jxl_memory_block check compares the size_t mallocSize against the uint64 bytesNeeded, which catches both wrap and 32-bit truncation for the >2 GB path.
- Division-by-zero guards are applied to all four black-level sites shown (GainMap, MapTable::Prepare, RefVignette32, RefBaselineMapPoly32); no other unguarded instance is visible in the material.
- The commit bundles several upstream changes; the material does not identify which of them is the CVE-2026-21353 issue (two bug IDs are referenced).
Behavior changes
- JXL content with chunk coordinates or image dimensions above 0x7fffffff, or whose stride/size products overflow, now aborts with a dng_exception (ThrowOverflow / DNG_REQUIRE failure) instead of proceeding.
- jxl_memory_block now throws ThrowMemoryFull on a null Malloc result for allocations above 2 GB.
- With blackLevel == 65535, GainMap/RefVignette32/RefBaselineMapPoly32 now use blackScale1 = 0.0 (blackOffset1 = 1.0) and MapTable::Prepare uses srcScale = 0.0, instead of dividing by zero.
- MapTable replication now copies the actual last entry (table[fCount-1]) into entries fCount..0xFFFF.
- SafeInt64Mult on Android clang builds now uses the __builtin_smull_overflow implementation.
- Reported SDK build string changes to 2471 (the latest security tags show 2502).
Per major
- Android 14: same fix: platform/external/dng_sdk 72d44e39d499 in android-security-14.0.0_r28, the full 1.7.1 build 2471 update; at android-security-14.0.0_r29, 5/8 lines of the patched region are present and dng_flags.h shows build 2502.
- Android 15: same fix: 7a737bb68ca6 in android-security-15.0.0_r16, identical diff; at android-security-15.0.0_r17, 5/8 lines of the patched region are present.
- Android 16: same fix: b0d9d2a8d100 in android-security-16.0.0_r7, identical diff; at android-security-16.0.0_r8, 5/8 lines of the patched region are present.
- Android 17: same fix in base: 9a9e97362f2d in android-17.0.0_r1, identical changes; only the dng_reference.cpp blob hashes and the RefBaselineMapPoly32 hunk offset differ (3011 vs 3009); android-security-17.0.0_r1 shows 5/8 lines of the patched region present.
Unknowns
- Which specific change in the bundled update corresponds to CVE-2026-21353 (the commit references bugs 483697751 and 483693973 without mapping them to hunks).
- Which Android component/process consumes dng_sdk's JXL decode or opcode paths with untrusted input.
- Whether code after the chunk readers validates the computed fArea against the destination image bounds.
- The declared type of dng_pixel_buffer::fRowStep and how wrapped strides were used downstream.
- Which 3 of the 8 patched-region lines are absent at the latest security tags, and what changed between build 2471 and 2502.
- Whether trees older than build 2410 contain dng_jxl.cpp.