Project

General

Profile

Actions

Bug #1239

closed

Out of bounds access into Lookup Table on big endian machines

Added by Michael Onken about 7 hours ago. Updated about 6 hours ago.

Status:
Closed
Priority:
Normal
Assignee:
Category:
-
Target version:
-
Start date:
2026-07-03
Due date:
% Done:

0%

Estimated time:
Module:
dcmimgle
Operating System:
Compiler:

Description

As reported by quellsec.dev:

Summary

On big-endian hosts, DiLookupTable::checkTable expands an 8-bit-allocated DICOM lookup table into a freshly allocated 16-bit buffer of Count entries but the byte-swapping copy loop writes Count+1 entries when NumberOfTableEntries is odd, overrunning the buffer by one Uint16. The little-endian path writes the correct number of entries, so this is specific to big-endian builds (s390x, ppc/ppc64 BE, SPARC).

Affected version

  • Target: dcmtk (dcmimgle)
  • Commit read: 7246c5a9ca64c2d4312774bf40d046e255c00a41
  • Bug is in library code (dcmimgle/libsrc/diluptab.cc).

Crash

AddressSanitizer: heap-buffer-overflow, WRITE of size 2, 0 bytes to the right of the Count-entry region.

Crash site: dcmimgle/libsrc/diluptab.cc:246 in DiLookupTable::checkTable.

Root cause

The 8-bit-allocated branch allocates Count 16-bit entries, then on a big-endian host runs a loop of count = (Count+1)>>1 iterations that writes two entries each:

dcmimgle/libsrc/diluptab.cc:231

bc(cpp). if (count == ((Count + 1) >> 1)) // bits allocated 8, ignore padding

dcmimgle/libsrc/diluptab.cc:235

bc(cpp). DataBuffer = new Uint16[Count]; // create new LUT

dcmimgle/libsrc/diluptab.cc:240

bc(cpp). if (gLocalByteOrder == EBO_BigEndian) // local machine has big endian byte ordering

dcmimgle/libsrc/diluptab.cc:243

bc(cpp). for (i = count; i != 0; --i) // copy 8 bit entries to new 16 bit LUT (swap hi/lo byte)

dcmimgle/libsrc/diluptab.cc:245

bc(cpp). *(q++) = *(p + 1); // copy low byte ...

dcmimgle/libsrc/diluptab.cc:246

bc(cpp). *(q++) = *p; // ... and then high byte

The loop performs 2*count stores. For odd Count, count = (Count+1)/2 and 2*count = Count+1, so the final *(q++) = *p (line 246) writes DataBuffer[Count], one element past the end. The stored value is a byte of the attacker-controlled LUTData. The little-endian branch is correct:

dcmimgle/libsrc/diluptab.cc:250

bc(cpp). for (i = Count; i != 0; --i)

Reproduction

The PoC builds a LUTDescriptor (0028,3002) with an odd NumberOfTableEntries and a matching (Count+1)/2-word LUTData (0028,3006), then constructs a DiLookupTable. On the little-endian test host it sets gLocalByteOrder to EBO_BigEndian so the library takes its own big-endian branch; the allocation, branch selection, and copy loop all run unmodified inside libdcmimgle and abort at diluptab.cc:246. Reproduction status: yes-rebuilt-and-ran (big-endian branch forced on an LE host; not run on a physical BE machine).

Proof of Concept

Self-contained. docker build clones the target at the pinned commit and builds it under AddressSanitizer + UndefinedBehaviorSanitizer; docker run feeds the crafted input and reproduces the fault. Save the files below into a poc/ directory and:

bc. docker build -t poc . && docker run --rm poc

Sanitizer output

.dockerignore (crafted input)

crafted_lut_elements.bin (26-byte binary input

build.sh

run.sh

Dockerfile

harness.cc

Impact

Controlled 2-byte heap out-of-bounds write past an exact-size LUT buffer, driven by a malicious DICOM LUT with an odd entry count, during image rendering. Genuine memory corruption, but only on big-endian builds; little-endian deployments are unaffected. Severity: medium overall (high on big-endian deployments).

Suggested fix

Drive the big-endian loop by the destination index so it writes exactly Count entries (matching the allocation and the little-endian branch), handling the trailing odd byte explicitly instead of always writing two entries per source word.

<hr />

All quoted code verified present in source at commit 7246c5a9ca64c2d4312774bf40d046e255c00a41 (snippet gate: OPEN, 7/7 PASS).

Actions #1

Updated by Michael Onken about 6 hours ago

  • Status changed from New to Closed

Fixed with commit 87cccaa50770a00169cf59ec18c63d42175a8d1d.

Actions

Also available in: Atom PDF