Project

General

Profile

Bug #1193 » IC-DCMTK-0006_REPORT.md

Jörg Riesmeier, 2026-03-10 23:46

 

IC-DCMTK-0006: Heap Buffer Overflow in DcmJSONReader getTokenContent()

Version: DCMTK master 418274445 (DCMTK-3.7.0+64)
CWE: CWE-122 (Heap-based Buffer Overflow)

Description

The DcmJSONReader::getTokenContent() function at dcmdata/libsrc/dcjsonrd.cc:221 accesses the internal buffer jsonDataset_ using JSMN token position fields (t->start, t->end) without validating them against the buffer bounds. When the JSMN tokenizer parses structurally incomplete JSON (e.g., an object key without a corresponding value), parseElement() reads past the end of the valid token array into uninitialized sentinel memory, producing tokens with extreme position values (start=-1, end=-1) that cause out-of-bounds access on the JSON input buffer.

The JSMN tokenizer uses a two-pass approach: pass 1 counts tokens (no bracket validation), pass 2 fills positions (with validation). This creates a discrepancy where pass 1 succeeds but pass 2 fails partway through, leaving unfilled tokens with sentinel values of -1. When parseElement() advances to read these sentinel tokens, getTokenContent() performs OOB reads and writes:

// dcjsonrd.cc:219-228
void DcmJSONReader::getTokenContent(OFString& value, OFJsmnTokenPtr t)
{
    int size = t->end - t->start;           // With sentinel: (-1) - (-1) = 0
    char c = jsonDataset_[t->start+size];   // LINE 221: OOB access at jsonDataset_[-1]
    jsonDataset_[t->start+size] = '\0';     // OOB write corrupts heap
    value = jsonDataset_ + t->start;        // OOB pointer arithmetic
    jsonDataset_[t->start+size] = c;        // OOB write restoring byte
}

A malformed JSON input as small as 19 bytes ({"00100010":{"vr"}}) triggers this vulnerability with the default json2dcm invocation. The vulnerability is reachable through three distinct call paths in parseElement() (lines 604, 642) and parseElementValueArray() (line 1022).

Reproduction

echo '{"00100010":{"vr"}}' > poc.json
./bin/json2dcm poc.json /dev/null

Actual output:

AddressSanitizer:DEADLYSIGNAL
=================================================================
==2185557==ERROR: AddressSanitizer: SEGV on unknown address 0x7c3076f6c37f (pc 0x555555787614 bp 0x7fffffffc450 sp 0x7fffffffc350 T0)
==2185557==The signal is caused by a READ memory access.
    #0 0x555555787614 in DcmJSONReader::getTokenContent(OFString&, jsmntok*) dcmdata/libsrc/dcjsonrd.cc:221:14
    #1 0x5555557908ef in DcmJSONReader::parseElement(DcmItem*, DcmItem*, jsmntok*&) dcmdata/libsrc/dcjsonrd.cc:642:9
    #2 0x555555797e03 in DcmJSONReader::parseDataSet(DcmItem*, DcmItem*, jsmntok*&) dcmdata/libsrc/dcjsonrd.cc:923:18
    #3 0x5555557a19c7 in DcmJSONReader::readAndConvertJSONFile(DcmFileFormat&, char const*) dcmdata/libsrc/dcjsonrd.cc:1279:18
    #4 0x55555571339b in main dcmdata/apps/json2dcm.cc:435:25
    #5 0x7ffff7989082 in __libc_start_main /build/glibc-LcI20x/glibc-2.31/csu/../csu/libc-start.c:308:16

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV dcmdata/libsrc/dcjsonrd.cc:221:14 in DcmJSONReader::getTokenContent(OFString&, jsmntok*)
==2185557==ABORTING

Fix

Add bounds validation in getTokenContent() to reject tokens whose boundaries extend beyond the JSON input buffer:

void DcmJSONReader::getTokenContent(OFString& value, OFJsmnTokenPtr t)
{
    int size = t->end - t->start;
    if (t->start < 0 || t->end < 0 || t->start + size < 0 ||
        static_cast<size_t>(t->start + size) >= jsonDatasetLen_)
    {
        value = "";
        return;
    }
    char c = jsonDataset_[t->start+size];
    jsonDataset_[t->start+size] = '\0';
    value = jsonDataset_ + t->start;
    jsonDataset_[t->start+size] = c;
}
(3-3/3)