All Advisories

Orthanc DICOM Server

Heap Buffer Overflow in DICOM Image Decoder (Palette Color Decode)

Orthanc's PALETTE COLOR image decode path performs pixel-data length validation and target-buffer allocation using 32-bit arithmetic. Crafted DICOM dimensions cause both multiplications to wrap to a small value, the validation passes against the wrapped result, and the decode loop then iterates with the original (non-wrapped) dimensions — reading past the pixel buffer and writing past the target buffer.

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

Description

When Orthanc decodes a PALETTE COLOR image it first validates that the supplied pixel data length matches the expected width * height, then allocates a target RGB24 buffer sized by the same arithmetic. Both calculations use unsigned int (32-bit). With Cols=1431655766 and Rows=3, the multiplication wraps:

OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp:463

// all operands are unsigned int (32-bit)
// With Cols=1431655766, Rows=3: 1431655766 * 3 = 4294967298 → uint32: 2
// pixelLength=2 matches overflowed result → validation passes
if (pixelLength != target->GetWidth() * target->GetHeight())

View source →

An attacker who controls the pixel data length can match it to the wrapped value (2 bytes), and the validation passes. The same overflow then drives the target buffer allocation. For RGB24 the pitch is 3 * width; with width=1431655766 this also wraps to 2, producing a tiny allocation of 6 bytes (pitch * height = 2 * 3):

OrthancFramework/Sources/Images/ImageBuffer.cpp:49

// RGB24 pitch: 3 * 1431655766 → uint32: 2
pitch_ = GetBytesPerPixel() * width_;
// Total allocation: 2 * 3 = 6 bytes (should be ~4 GB for RGB24)

View source →

The decode loop then runs with the original (non-wrapped) dimensions: 1.4 billion pixel iterations against a 2-byte pixel buffer (OOB read) and a 6-byte target buffer (OOB write). The first iteration past the buffer's third pixel triggers both primitives:

OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp:491-503

// iterates 1431655766 * 3 times
// At pixel #3: source reads past 2-byte pixel buffer (OOB read)
// At pixel #3: p writes past 6-byte target buffer (OOB write)
for (unsigned int x = 0; x < width; x++)
{
p[0] = lutRed[*source] >> offsetBits; // OOB read via *source
p[1] = lutGreen[*source] >> offsetBits;
p[2] = lutBlue[*source] >> offsetBits; // OOB write via p[2]
source++;
p += 3;
}

View source →

The enabler is the same VR UL dimension bypass used by CVE-2026-5442: DICOM defines Rows/Columns as VR US (max 65535), but DCMTK in Explicit VR Little Endian transfer syntax reads the VR from the file. Encoding Rows/Columns with VR UL allows arbitrary 32-bit values that Orthanc accepts without validation. With standard VR US values the multiplication cannot wrap (max 65535*65535 = 4,294,836,225 < uint32 max), so the VR UL bypass is essential.

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

AddressSanitizer trace

==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xffffa6887072
READ of size 1 at 0xffffa6887072 thread T18
#0 in DecodeLookupTable DicomImageDecoder.cpp:497
#1 in Orthanc::DicomImageDecoder::DecodeUncompressedImage() DicomImageDecoder.cpp:575
#2 in Orthanc::DicomImageDecoder::Decode() DicomImageDecoder.cpp:827
#3 in Orthanc::ParsedDicomFile::DecodeFrame() ParsedDicomFile.cpp:1823
#4 in Orthanc::ServerContext::DecodeDicomFrame() ServerContext.cpp:1921
0xffffa6887072 is located 0 bytes to the right of 2-byte region [0xffffa6887070,0xffffa6887072)
allocated by thread T18 here:
#0 in operator new[]
#1 in DcmElement::newValueField() dcelem.cc:788

On a stock build the loop walks past the small heap region until it hits an unmapped page; the Orthanc process terminates. Reachability matches CVE-2026-5445: authenticated HTTP upload + preview, or unauthenticated C-STORE injection on port 4242 under the default DicomAlwaysAllowStore: true. Once the malicious DICOM is stored, every preview request from any user crashes the server — the malicious DICOM is persistent and reproduces the crash on every render.

Impact

  • Persistent denial of service: a single stored DICOM crashes the Orthanc process every time any user requests its preview. Restart returns service but the malicious DICOM remains; any subsequent preview crashes the server again.
  • Heap-write primitive: the loop writes sequential RGB triplets (3 bytes per pixel) past a 6-byte target buffer with controlled pixel-derived bytes. Code execution within the Orthanc process's authority has not been demonstrated; the primitive's shape (sequential, controlled bytes, multi-gigabyte span) makes it a plausible target for further exploitation research.
  • Reachable unauthenticated for the planting step under Orthanc's default configuration (DicomAlwaysAllowStore: true); the trigger requires only a preview request from any read-level user.

Mitigation

Update Orthanc to version 1.12.11 or later. The fix uses 64-bit arithmetic for all pixel-data length validation and combines it with rejection of Rows/Columns values that exceed the VR US range (65535) at parse time — the same dimension validation that mitigates CVE-2026-5442. 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. Otherwise any host that can reach port 4242 can plant the persistent crashing DICOM unauthenticated.

  • Quarantine and review the DICOM store before patching.

    Search the store for studies whose Rows or Columns exceed 65535 (only possible with VR UL encoding). Such studies are by construction malicious, since DICOM defines these tags as VR US (max 65535). Quarantine before patching, since the next preview request crashes the server.

  • Audit who has preview access.

    Once a malicious study is stored, any read-level user requesting GET /instances/*/preview crashes the process. Limit preview access at your reverse proxy to the IPs of integration points and viewers that need it.

  • Watch for crash-restart cycles tied to preview requests.

    Alert on Orthanc restarts (journalctl -u orthanc | grep -E 'SIGSEGV|killed') that closely follow GET /instances/*/preview log entries. Repeated crashes from the same study ID indicate a stored attack rather than a transient fault.

  • Confirm crash recovery is configured.

    Until the store is purged of the malicious study, every preview crashes the process. Tight supervisor restart (systemd, Kubernetes liveness probes, Docker --restart) keeps service available between requests but does not mitigate the bug.

Severity Reasoning

AV:NReachable over the network via Orthanc's HTTP REST API and via DICOM C-STORE on port 4242.AC:LA single crafted DICOM file with VR UL dimensions and 2-byte pixel data. No timing or environmental conditions.PR:NThe published vector reflects deployments where preview rendering is exposed without HTTP authentication and the C-STORE listener accepts unauthenticated requests under default DicomAlwaysAllowStore: true.UI:NNo interaction by a second user is needed; planting and triggering can both be performed by the same actor.S:UOnly the Orthanc process is affected; no out-of-process resources are reached through this primitive.C:HPlausible RCE within the Orthanc process's authority would expose process memory and any data it can read. Not demonstrated.I:HSame primitive within the same authority would allow modifying in-memory state and any data the process can write. Not demonstrated.A:HPersistent crash: the stored DICOM crashes the process on every preview request from any user.

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.