All Advisories

Orthanc DICOM Server

Out-of-Bounds Read in DICOM Image Decoder (PMSCT_RLE1 Decompression)

Orthanc decodes the proprietary Philips PMSCT_RLE1 image format with two passes (RLE then delta). Both passes read two bytes ahead when they encounter an escape marker (0xa5 / 0x5a), but neither pass checks that those two bytes lie within the input buffer. Markers positioned near the end of the buffer cause out-of-bounds reads whose bytes flow into the rendered preview.

Authored byVolker Schönefeld, Simon WeberDisclosed 2026-04-02Fully disclosed 2026-04-28
SeverityHighCVSS 7.1CVSS 3.1 VectorAV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:HCWECWE-125 (Out-of-bounds Read)ProductOrthanc DICOM ServerAffected VersionsOrthanc <= 1.12.10Fixed In1.12.11CVECVE-2026-5441CERT/CCVU#536588.4

Description

PMSCT_RLE1 is a proprietary RLE-plus-delta compression format used by certain Philips medical imaging modalities. Orthanc reaches the decoder when a DICOM file omits the standard PixelData tag (7FE0,0010) but provides the Philips private tags (0x07a1,0x1011) = "PMSCT_RLE1" and (0x07a1,0x100a) with the compressed payload — ImageSource::Setup falls through to DecodePsmctRle1().

The first pass implements run-length expansion. When it encounters the escape byte 0xa5, it reads two follow-up bytes — a repeat count (i+1) and a value to repeat (i+2) — without checking whether those offsets lie within the input buffer:

OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp:191-206 (RLE pass)

for (size_t i = 0; i < length; i++)
{
if (inbuffer[i] == 0xa5)
{
temp.push_back(inbuffer[i+2]); // OOB read if i >= length-2
for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--)
{
temp.push_back(inbuffer[i+2]); // OOB read repeated up to 255 times
}
i += 2;
}
// ...
}

View source →

The second pass implements delta-value reconstruction. When it sees 0x5a, it reads two follow-up bytes (i+1, i+2) and combines them into a 16-bit value without checking the bounds:

OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp:212-231 (Delta pass)

for (size_t i = 0; i < temp.size(); i++)
{
if (temp[i] == 0x5a)
{
uint16_t v1 = temp[i + 1]; // OOB read if i >= temp.size()-1
uint16_t v2 = temp[i + 2]; // OOB read if i >= temp.size()-2
value = (v2 << 8) + v1;
i += 2;
}
// ...
}

View source →

The RLE pass is the more useful primitive: a single 0xa5 placed near the buffer's end with a repeat count of 255 reads the same out-of-bounds byte 256 times into the output stream, producing a controlled amplification of one heap byte across hundreds of pixels in the rendered preview.

In a 24-byte test payload (\x00*22 + \xa5\xff) sized to fill a glibc 32-byte chunk, the 0xa5 escape at position 22 reads inbuffer[24] — the next heap chunk's metadata — and replays it 256 times. The delta pass then accumulates these bytes into ascending pixel intensities:

Preview output (8x8, mode=L)

row 0: [ 0, 0, 0, 0, 0, 0, 0, 0]
row 1: [ 0, 0, 0, 0, 0, 0, 0, 0]
row 2: [ 0, 0, 0, 0, 0, 0, 6, 12]
row 3: [ 18, 24, 30, 36, 43, 49, 55, 61]
row 4: [ 67, 73, 79, 85, 91, 97, 103, 109]
row 5: [115, 121, 128, 134, 140, 146, 152, 158]
row 6: [164, 170, 176, 182, 188, 194, 200, 206]
row 7: [213, 219, 225, 231, 237, 243, 249, 255]

AddressSanitizer pinpoints the first OOB at DicomImageDecoder.cpp:195, immediately past a 24-byte heap region:

AddressSanitizer trace

==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xffffa5437348
READ of size 1 at 0xffffa5437348 thread T18
#0 in std::vector<unsigned char>::push_back() stl_vector.h:1192
#1 in Orthanc::DicomImageDecoder::DecodePsmctRle1() DicomImageDecoder.cpp:195
#2 in Orthanc::DicomImageDecoder::ImageSource::Setup() DicomImageDecoder.cpp:275
#3 in Orthanc::DicomImageDecoder::DecodeUncompressedImage() DicomImageDecoder.cpp:558
#4 in Orthanc::DicomImageDecoder::Decode() DicomImageDecoder.cpp:827
#5 in Orthanc::ParsedDicomFile::DecodeFrame() ParsedDicomFile.cpp:1823
#6 in Orthanc::ServerContext::DecodeDicomFrame() ServerContext.cpp:1921
0xffffa5437348 is located 0 bytes to the right of 24-byte region [0xffffa5437330,0xffffa5437348)
allocated by thread T18 here:
#0 in operator new[]
#1 in DcmElement::newValueField() dcelem.cc:788

The vulnerability is reachable via the same two paths as CVE-2026-5445 (DecodeLookupTable): authenticated HTTP upload + preview, or unauthenticated C-STORE injection on port 4242 under the default DicomAlwaysAllowStore: true configuration. Varying the payload size across requests scans different heap offsets, building a multi-byte heap dump.

Impact

  • Information disclosure of arbitrary heap memory via rendered preview images, with controlled amplification (one OOB byte repeated up to 256 times in the output) provided by the RLE repeat count.
  • By varying the input payload size, the attacker scans different heap offsets across multiple crafted files, building a multi-byte heap dump.
  • Reachable unauthenticated for the planting step under Orthanc's default configuration (DicomAlwaysAllowStore: true); the rendering step requires read-level authentication.
  • On builds with strict heap layout (e.g. AddressSanitizer or hardened glibc), an OOB read may abort the process; on stock builds the typical outcome is a silent data leak.

Mitigation

Update Orthanc to version 1.12.11 or later. The fix adds bounds checks before every i+1/i+2 access in both passes, aborting decode with BadFileFormat when an escape marker is followed by insufficient data. As immediate defense in depth, set DicomAlwaysAllowStore: false in orthanc.json and configure an AET allow-list, which closes the unauthenticated planting vector.

Defender's Checklist

  • Verify your version.

    curl -u <user>:<pass> http://<orthanc>:8042/system | jq .Version — the patched range begins at 1.12.11.

  • Close the unauthenticated C-STORE planting vector.

    Set DicomAlwaysAllowStore: false in orthanc.json and define an explicit DicomModalities allow-list of legitimate AETs. Without this, any host that can reach port 4242 can plant the malicious DICOM unauthenticated.

  • Restrict the preview endpoint.

    If your deployment does not require web preview, restrict GET /instances/*/preview at your reverse proxy. The OOB only becomes observable when something renders the malicious image.

  • Audit who has read access.

    An attacker only needs read-level credentials to view the leaked heap data. Review RegisteredUsers in orthanc.json and any external authentication backend for stale or low-privilege accounts that should not have read access to arbitrary studies.

  • Audit the DICOM store for PMSCT_RLE1 payloads.

    Search the store for studies that omit (7FE0,0010) PixelData but carry (0x07a1,0x1011) = "PMSCT_RLE1". Studies from sources outside your modality inventory or with unexpected payload sizes should be quarantined and reviewed before patching.

Severity Reasoning

AV:LThe published vector treats the path as local-vector: an actor uploads or stores a malicious DICOM file that another agent later renders. Note the bug is also reachable network-side via HTTP REST and via C-STORE on port 4242.AC:LA single crafted DICOM with a near-end-of-buffer RLE escape; no environmental conditions.PR:NThe published vector reflects deployments where the C-STORE listener accepts unauthenticated requests under default DicomAlwaysAllowStore: true and where preview rendering is exposed without HTTP authentication.UI:RA user (the same actor or another viewer) must request the preview to render the malicious DICOM and trigger the OOB read.S:UOnly the Orthanc process is affected; no out-of-process resources are reached through this primitive.C:HControlled heap-content disclosure across many output pixels per request, with payload-size variation enabling sweeps across heap offsets.I:NRead-only primitive; nothing is modified.A:HOn builds with strict heap layout the OOB read can reach an unmapped memory page, terminating the Orthanc process.

References

How We Can Help

Who We Are

The security researchers behind this advisory.

Dr. Simon Weber Profile

Dr. rer. nat. Simon Weber

Senior Pentester & MedSec Researcher

I evaluate your SaMD with the same industry-defining security insight I contributed to the BAK MV for the revision of the B3S standard.

  • PhD on Hospital Cybersecurity
  • Critical vulnerabilities found in hospital systems
  • Alumni of THB MedSec Research Group
  • gematik Security Hero
Volker Schönefeld Profile

Dipl.-Inf. Volker Schönefeld

Senior Application Security Expert

As a former CTO and developer turned pentester, I work alongside your team to uncover vulnerabilities and find solutions that fit your architecture.

  • 20+ years as CTO, 50M+ app downloads
  • Architected and secured large-scale IoT fleets
  • Certified Web Exploitation Specialist
  • gematik Security Hero

Looking for a Penetration Test?

Machine Spirits specializes in security assessments for medical devices and healthcare IT. From MDR penetration testing to C5 cloud compliance, we help MedTech companies meet regulatory requirements.