Project

General

Profile

Actions

Bug #1240

closed

Heap buffer overflow in multi-frame overlay conversion.

Added by Michael Onken about 5 hours ago. Updated about 5 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

DiOverlayPlane::create6xxx3000Data sizes its output buffer as a single continuous bitstream of Width*Height*Frames bits, but it emits the overlay frame by frame and flushes a partial byte at the end of each frame. When the overlay plane size (Width*Height) is not a multiple of 8, every frame contributes one extra byte that the buffer size did not reserve, so a multi-frame overlay overruns the allocation.

Reachable when an application calls DicomImage::create6xxx3000OverlayData() on an attacker-supplied DICOM object carrying a repeating-group (60xx) overlay.

Affected version

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

Crash

AddressSanitizer: heap-buffer-overflow, WRITE of size 1, 0 bytes to the right of a 1250-byte region (for the PoC parameters).

Crash site: dcmimgle/libsrc/diovpln.cc:527 in DiOverlayPlane::create6xxx3000Data.

Root cause

The output is sized as one packed bitstream:

dcmimgle/libsrc/diovpln.cc:492

bc(cpp). const unsigned long count = OFstatic_cast(unsigned long, Width) * OFstatic_cast(unsigned long, Height) * NumberOfFrames;

dcmimgle/libsrc/diovpln.cc:495

bc(cpp). const unsigned long count8 = ((count + 15) / 16) * 2; // round value: 16 bit padding

dcmimgle/libsrc/diovpln.cc:496

bc(cpp). buffer = new Uint8[count8];

but the emit loop runs per frame and flushes a partial byte after each frame:

dcmimgle/libsrc/diovpln.cc:505

bc(cpp). for (unsigned long f = 0; f < NumberOfFrames; ++f)

dcmimgle/libsrc/diovpln.cc:517

bc(cpp). *(q++) = value;

dcmimgle/libsrc/diovpln.cc:526

bc(cpp). if (bit != 0)
*(q++) = value;

count8 is ceil(count/8) rounded to an even length, i.e. the size of one continuous bitstream. Because the bit position is not realigned per frame in that calculation, a plane whose Width*Height is not a multiple of 8 produces one extra partial byte per frame. With OverlayRows = OverlayColumns = 1 (1 bit/frame) and NumberOfFramesInOverlay = 10000, count8 = ((10000+15)/16)*2 = 1250 bytes, but the per-frame flush attempts ~10000 stores, so q runs off the 1250-byte buffer.

Reproduction

poc/crash_overlay.dcm is a 4x4 MONOCHROME2 image with a 1x1 overlay in group 0x6000 and NumberOfFramesInOverlay = 10000. Building a DicomImage and calling create6xxx3000OverlayData(buffer, 0x6000, ...) aborts under ASan with the trace above. Reproduction status: yes-rebuilt-and-ran.

Call path:

bc. DicomImage::create6xxx3000OverlayData (dcmimage.h:1354)
DiMonoImage::create6xxx3000OverlayData (dimoimg.cc:1619)
DiOverlay::create6xxx3000PlaneData (diovlay.cc:598)
DiOverlayPlane::create6xxx3000Data (diovpln.cc:527)

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)

crash_overlay.dcm (5904-byte binary crafted input) is produced by gen_dcm.sh (inlined below) -- the PoC builds it at run time, so there is no pre-built blob to fetch.

build.sh

run.sh

Dockerfile

gen_dcm.sh

harness.cc

Impact

Heap out-of-bounds write whose total overrun length scales with the attacker-controlled NumberOfFramesInOverlay, host-byte-order independent. Memory corruption beyond a heap allocation; demonstrated impact is a crash (DoS). Severity: high. Not claimed as RCE without a working control-flow hijack.

Suggested fix

Make the allocation match the per-frame flush, e.g. count8 = NumberOfFrames * (((Width*Height) + 15) / 16) * 2, or realign the bit writer to a byte boundary deterministically at frame end so the packed stream matches the count8 budget.

<hr />

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

Actions #1

Updated by Michael Onken about 5 hours ago

  • Status changed from New to Closed

Fixed with commit d0a6f8afaecc676dfc8e36a0ee1a729455a7f74f.

Actions

Also available in: Atom PDF