All Advisories

Orthanc DICOM Server

Heap Buffer Overflow in PAM Image Buffer Allocation

Orthanc's PAM image parser computes the buffer pitch using 32-bit unsigned integer arithmetic. Crafted dimensions cause the multiplication to wrap, producing a tiny allocation that the subsequent byte-swap loop then writes past, using the original (non-wrapped) width as its upper bound.

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:N/I:H/A:HCWECWE-787 (Out-of-bounds Write)ProductOrthanc DICOM ServerAffected VersionsOrthanc <= 1.12.10Fixed In1.12.11CVECVE-2026-5444CERT/CCVU#536588.1

Description

Orthanc accepts PAM (Portable Arbitrary Map) images embedded in POST /tools/create-dicom requests, where the PAM payload arrives base64-encoded inside the JSON Content field. The PAM parser in PamReader.cpp reads WIDTH, HEIGHT, DEPTH, and MAXVAL from the textual PAM header and uses them to compute the row pitch and total buffer size before allocating.

The pitch calculation multiplies three unsigned int values without overflow protection:

OrthancFramework/Sources/Images/PamReader.cpp:188

// width, channelCount, bytesPerChannel are all unsigned int (32-bit).
// e.g. WIDTH=715827883, DEPTH=3, MAXVAL=65535 (bytesPerChannel=2):
// 715827883 * 3 * 2 = 4294967298 (0x100000002)
// Truncated to uint32: 2
unsigned int pitch = width * channelCount * bytesPerChannel;

View source →

The truncated pitch then drives the buffer allocation:

OrthancFramework/Sources/Images/PamReader.cpp:214

// allocates pitch * height = 2 * 1 = 2 bytes
alignedImageBuffer_ = malloc(pitch * height);

View source →

An existing length check at PamReader.cpp:190 (content_.size() != header.size() + headerDelimiter.size() + pitch * height) does not catch the overflow, because the PAM file size is controlled by the same actor and can be chosen to match the truncated value.

The byte-swap loop then iterates using the original (non-truncated) width, writing two bytes per iteration far past the small allocation:

OrthancFramework/Sources/Images/PamReader.cpp:240-272

// loop iterates `width` times (e.g. 715827883)
// each iteration advances a uint16_t pointer, writing at
// offsets 0, 2, 4, ... across ~1.4 GB of heap past the buffer
for (unsigned int w = 0; w < width; ++w, ++pixel) {
uint8_t* srcdst = reinterpret_cast<uint8_t*>(pixel);
uint8_t tmp = srcdst[0]; // out-of-bounds read
srcdst[0] = srcdst[1]; // out-of-bounds write
srcdst[1] = tmp; // out-of-bounds write
}

View source →

The total payload is 71 bytes (PAM header plus two bytes of pixel data), base64-encoded into the JSON request body. AddressSanitizer pinpoints the first out-of-bounds access at PamReader.cpp:268, immediately past the 2-byte allocation made at PamReader.cpp:214:

AddressSanitizer trace

==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xffffabc81372
READ of size 1 at 0xffffabc81372 thread T16
#0 in Orthanc::PamReader::ParseContent() PamReader.cpp:268
#1 in Orthanc::PamReader::ReadFromMemory() PamReader.cpp:295
#2 in Orthanc::ParsedDicomFile::EmbedImage() ParsedDicomFile.cpp:1300
#3 in Orthanc::ParsedDicomFile::EmbedContent() ParsedDicomFile.cpp:1262
#4 in CreateDicomV2 OrthancRestAnonymizeModify.cpp:1029
#5 in CreateDicom OrthancRestAnonymizeModify.cpp:1108
0xffffabc81372 is located 0 bytes to the right of 2-byte region [0xffffabc81370,0xffffabc81372)
allocated by thread T16 here:
#0 in __interceptor_malloc
#1 in Orthanc::PamReader::ParseContent() PamReader.cpp:214

On a stock build without ASAN, the byte-swap loop walks into unmapped memory and the process terminates with SIGSEGV (exit code 139) within milliseconds of the request being handled.

Impact

  • Denial of service: a single authenticated POST /tools/create-dicom request with a 71-byte PAM payload terminates the Orthanc process with SIGSEGV. The server must be restarted after each request and is immediately vulnerable again.
  • Heap-write primitive: the loop performs sequential 2-byte swaps across roughly 1.4 GB of heap past the allocation. Code execution has not been demonstrated; the primitive's shape (controlled offsets, attacker-influenced bytes) makes it a plausible target for further exploitation research.
  • Attacker model depends on Orthanc's configuration: with AuthenticationEnabled set, an authenticated user with rights to upload or modify studies; without HTTP authentication (a permitted Orthanc configuration), any network-reachable caller.

Mitigation

Update Orthanc to version 1.12.11 or later. The fix performs both header-value validation at parse time and 64-bit arithmetic with explicit overflow checks before the allocation. As a defense-in-depth measure, operators can additionally restrict POST /tools/create-dicom via per-route authorization in orthanc.json.

Defender's Checklist

  • Verify your version.

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

  • Restrict the affected endpoint.

    Block or restrict POST /tools/create-dicom at your reverse proxy (nginx, Traefik, etc.) for any user role that does not need to create DICOM files programmatically.

  • Audit credentials.

    The bug requires an authenticated user. Review RegisteredUsers in orthanc.json and any external authentication backend for accounts that should not have create-dicom privileges.

  • Check logs for suspicious activity.

    Look for prior POST /tools/create-dicom calls with unusually small request bodies (under ~200 bytes) and for unexpected process restarts in your service supervisor (e.g. journalctl -u orthanc | grep -E 'SIGSEGV|killed').

  • Confirm crash recovery is configured.

    A successful denial of service should be bounded to the time-to-restart of your supervisor (systemd, Kubernetes liveness probes, Docker --restart). Recovery does not mitigate the bug, but limits the blast radius.

Severity Reasoning

AV:LThe published vector treats the path as local-vector: a user supplies a malicious PAM payload that the application processes. Note the bug is also reachable network-side via POST /tools/create-dicom.AC:LA single 71-byte PAM payload with overflowed dimensions; no timing, race, or environmental conditions.PR:NThe published vector reflects deployments where POST /tools/create-dicom is exposed without HTTP authentication, which Orthanc permits via configuration.UI:RA user (the same actor or another caller) must submit the malicious PAM payload via POST /tools/create-dicom to trigger the allocation and byte-swap loop.S:UOnly the Orthanc process is affected; no out-of-process resources are reachable through this primitive.C:NThe published vector does not credit the heap-write primitive with confidentiality impact. The byte-swap loop is a write primitive; any read of process memory would be a downstream consequence of plausible code execution and is not directly demonstrated.I:HPlausible RCE within the Orthanc process's authority would allow modifying in-memory state and any data the process can write. Not demonstrated.A:HA single request terminates the process with SIGSEGV.

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.