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.
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: 2unsigned int pitch = width * channelCount * bytesPerChannel;The truncated pitch then drives the buffer allocation:
OrthancFramework/Sources/Images/PamReader.cpp:214
// allocates pitch * height = 2 * 1 = 2 bytesalignedImageBuffer_ = malloc(pitch * height);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 bufferfor (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}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 0xffffabc81372READ 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:214On 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-dicomrequest 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
AuthenticationEnabledset, 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-dicomat 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
RegisteredUsersinorthanc.jsonand any external authentication backend for accounts that should not havecreate-dicomprivileges.Check logs for suspicious activity.
Look for prior
POST /tools/create-dicomcalls 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
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. 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

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.
