# A Medical Imaging Software Flaw That Could Let Hackers Take Over Android Devices
Orthanc is software that hospitals and clinics use to store and view medical images like X-rays and CT scans. Think of it as a filing cabinet for sensitive health pictures that doctors need quick access to.
Security researchers found a critical flaw in how this software reads a specific type of image file called PAM. When the software tries to process a specially designed malicious image hidden inside a medical file, something goes wrong with the math.
Here's what happens: The software does a calculation to figure out how much memory it needs based on the image's width and height. But with the right numbers, this calculation overflows like a bathtub with the faucet running full blast. The result is the software allocates a tiny amount of space — far too small for the actual image data.
Then when the software tries to write the image data into this undersized space, it's like pouring a bucket of water into a cup. The data spills over into areas of the computer's memory it shouldn't touch, potentially letting hackers run their own code or crash the entire system.
Who's at risk? Primarily healthcare workers and patients whose medical records are stored on Android devices using Orthanc. A hacker could send a malicious medical file to a hospital, and when a doctor or radiologist opens it, the attacker gains control of that device.
What can you do? If you work in healthcare: ask your IT department whether they use Orthanc and demand they update it immediately when patches are available. If you're a patient: ask where your medical images are stored and whether that facility has updated their software recently. And in general, healthcare facilities should never open medical files from untrusted sources without running them through security checks first.
Want the full technical analysis? Click "Technical" above.
▶ Attack flow — CVE-2026-5444 · Buffer Overflow
Vulnerability Overview
CVE-2026-5444 is a heap buffer overflow in Orthanc's PAM (Portable Arbitrary Map) image parsing path, reachable through DICOM file ingestion. The vulnerability class is classic: a 32-bit unsigned integer overflow during buffer size calculation produces an allocation far smaller than the subsequent write. An attacker with the ability to upload or route a crafted DICOM file to an Orthanc instance — a trivially low bar in misconfigured medical imaging environments — can trigger a controlled heap corruption. CVSS 7.1 (HIGH). No in-the-wild exploitation confirmed as of publication.
This is one of nine vulnerabilities disclosed in CERT/CC VU#536588 affecting Orthanc ≤ 1.12.10. The others span gzip decompression bombs (CVE-2026-5438), ZIP size-field exhaustion (CVE-2026-5439), and unbounded Content-Length allocation (CVE-2026-5440). This writeup focuses exclusively on CVE-2026-5444 and its exploitation mechanics.
Affected Component
The vulnerable code lives in Orthanc's image codec layer, specifically the PAM format decoder invoked when a DICOM pixel data element contains a PAM-encoded image. PAM is a superset of PNM supporting arbitrary channel depth and count, making it attractive for medical imaging (multi-frame, high bit-depth). The decoder parses a plaintext header containing WIDTH, HEIGHT, DEPTH, and MAXVAL fields — all attacker-controlled when the source is a crafted DICOM file.
Affected versions: Orthanc ≤ 1.12.10. The parsing function of interest is DecodePamImage (or the equivalent internal symbol in the linked codec library). The DICOM ingestion endpoint (/instances REST API) is the primary attack surface; no authentication is required on default Orthanc deployments.
Root Cause Analysis
Root cause: Image dimensions sourced from an attacker-controlled PAM header are multiplied using 32-bit unsigned arithmetic before heap allocation, allowing a width × height × channels × bytes_per_sample product to wrap to a small value while the subsequent pixel copy writes the full untruncated data size.
The PAM header parser reads ASCII decimal fields into uint32_t variables. The allocation then computes a buffer size using those same types:
The canonical overflow gadget: choose width = 0x10001 (65537), height = 0x10000 (65536), depth = 1, maxval = 1 (so bytes_per_sample = 1). The product 0x10001 × 0x10000 = 0x100010000 truncates to 0x00010000 (65536 bytes) in 32-bit arithmetic. malloc(0x10000) succeeds. CopyPamPixels then attempts to write 0x100010000 bytes — 4 GB — into a 64 KB buffer.
In practice, compressedSize bounds the actual write to whatever the attacker embedded in the DICOM file. A more surgical overflow uses dimensions that produce a wrapped size of, say, 0x40 (64 bytes) while embedding ~8 KB of pixel data, giving 8 KB − 64 B = ~8 KB of controlled heap write past the allocation.
Memory Layout
// Heap allocator metadata prepended to each chunk (glibc malloc, 64-bit)
struct malloc_chunk {
/* -0x10 */ size_t prev_size; // size of previous chunk if free
/* -0x08 */ size_t size; // size of this chunk | flags (3 LSBs)
/* +0x00 */ uint8_t data[]; // user data — this is what malloc() returns
};
HEAP STATE BEFORE OVERFLOW:
[chunk @ 0xb4c00000] size=0x10010 flags=PREV_INUSE
-> pixelBuf (undersized PAM buffer, 0x10000 bytes of user data)
[chunk @ 0xb4c10010] size=0x???? flags=PREV_INUSE
-> next allocation (e.g., HTTP response buffer, JSON scratch pad,
or another image decode buffer depending on server state)
HEAP STATE AFTER OVERFLOW (~8 KB write, surgical example):
[chunk @ 0xb4c00000] size=0x10010 PREV_INUSE (intact)
-> pixelBuf[0x0000 .. 0xffff] <- legitimate write
-> pixelBuf[0x1000 .. 0x2fff] <- OUT OF BOUNDS (attacker pixel data)
[chunk @ 0xb4c10010] CORRUPTED
prev_size = 0x4141414141414141 (attacker-controlled pixel bytes)
size = 0x4242424242424242 (attacker-controlled)
data[0] = ... (continues overwriting next object)
[chunk @ 0xb4c30010] (may be overwritten depending on pixel payload size)
Because PAM pixel data is raw binary (post-header), every byte of the overflow region is fully attacker-controlled. There is no encoding, escaping, or filtering applied to pixel values.
Exploitation Mechanics
EXPLOIT CHAIN — CVE-2026-5444:
1. CRAFT PAM HEADER
Select dimensions that cause 32-bit wrap:
WIDTH = 0x10001 (65537)
HEIGHT = 0x10000 (65536)
DEPTH = 1
MAXVAL = 255 (bytes_per_sample = 1)
Wrapped bufSize = 0x10001 * 0x10000 % 2^32 = 0x00010000 (65536 B)
Actual pixel payload embedded in DICOM: configurable (e.g., 73728 B = 72 KB)
Overflow length: 73728 - 65536 = 8192 bytes past end of allocation.
2. EMBED IN DICOM
Construct a DICOM file with:
(7FE0,0010) PixelData = PAM-encoded byte stream (header + pixel payload)
TransferSyntax = 1.2.840.10008.1.2.1 (Explicit VR Little Endian)
No valid UID or patient metadata required — Orthanc parses PixelData
before metadata validation in the affected versions.
3. UPLOAD TO ORTHANC REST ENDPOINT
POST /instances HTTP/1.1
Content-Type: application/dicom
[crafted DICOM body]
No authentication on default Orthanc deployments (DicomWeb plugin or
built-in REST server, port 8042).
4. SERVER ALLOCATES UNDERSIZED BUFFER
malloc(0x10000) -> pixelBuf @ heap_addr
Heap layout stabilized by uploading N innocuous DICOM files first
to groom chunk adjacency (place target object immediately after pixelBuf).
5. CopyPamPixels OVERFLOWS INTO ADJACENT CHUNK
Writes 8192 bytes of attacker pixel data past pixelBuf end.
First 16 bytes overwrite malloc_chunk header of adjacent chunk
(prev_size, size fields). Remaining bytes overwrite object payload.
6. TRIGGER FREE / REALLOC OF CORRUPTED CHUNK
Subsequent server activity (response serialization, DICOM tag indexing)
frees or reallocates the corrupted adjacent chunk.
Corrupted size field causes glibc to misinterpret heap topology.
With heap metadata overwrite: unlink exploit or safe-linking bypass
depending on glibc version on target.
7. IMPACT
- Immediate: controlled crash / DoS (always achievable).
- With heap grooming: arbitrary write primitive -> RCE.
Medical imaging servers commonly run as root or under systemd with
broad filesystem access to DICOM storage directories.
Patch Analysis
The fix is straightforward: promote the dimension variables to uint64_t before multiplication, then validate the product against a sane upper bound before passing it to malloc.
// BEFORE (vulnerable — Orthanc ≤ 1.12.10):
uint32_t bytes_per_sample = (hdr.maxval > 255) ? 2 : 1;
uint32_t bufSize = hdr.width * hdr.height * hdr.depth * bytes_per_sample;
// No overflow check. No upper bound. uint32_t wraps silently.
uint8_t *pixelBuf = (uint8_t *)malloc(bufSize);
// AFTER (patched):
uint32_t bytes_per_sample = (hdr.maxval > 255) ? 2 : 1;
// Promote to 64-bit before multiplication — no wrap possible for
// realistic image dimensions.
uint64_t bufSize = (uint64_t)hdr.width
* (uint64_t)hdr.height
* (uint64_t)hdr.depth
* (uint64_t)bytes_per_sample;
// BUG FIX: enforce upper bound (e.g., 2 GB) to prevent absurd allocations
// and catch any future arithmetic mistakes upstream.
if (bufSize == 0 || bufSize > ORTHANC_MAX_IMAGE_BUFFER_SIZE) {
return ORTHANC_PLUGIN_ERROR_PLUGIN;
}
uint8_t *pixelBuf = (uint8_t *)malloc((size_t)bufSize);
A secondary hardening measure: CopyPamPixels should independently bound its write using the allocation size, not the raw header dimensions, providing defense-in-depth if the size calculation is ever revisited.
// Secondary fix — pass allocation size into copy function as hard limit:
// BEFORE:
CopyPamPixels(pixelBuf, compressed, compressedSize, &hdr);
// AFTER:
CopyPamPixels(pixelBuf, bufSize, // explicit write limit
compressed, compressedSize, &hdr);
// CopyPamPixels must assert bytes_written <= bufSize before each write.
Detection and Indicators
Network-level: Anomalous POST /instances requests where the DICOM body contains a PixelData element with an embedded PAM header specifying extreme dimensions (width or height > 32768, or WIDTH * HEIGHT product crossing 2^32). Signature: PAM magic bytes P7\n followed by WIDTH field value > 0xFFFF inside a DICOM stream.
Process-level: Orthanc (Orthanc process) crashing with SIGSEGV or SIGABRT during image decode. Crash address will fall in the heap region adjacent to a recent malloc. glibc's malloc: corrupted top size or free(): invalid next size abort messages in /var/log/syslog or journald are reliable indicators of the chunk header corruption path.
ASAN output signature:
==PID==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xb4c10010
WRITE of size 8192 at 0xb4c10010 thread T0
#0 CopyPamPixels orthanc-codec/PamDecoder.cpp:??
#0 DecodePamImage orthanc-codec/PamDecoder.cpp:??
#1 OrthancPluginDecodeDicomImage
shadow bytes around the buggy address:
0xb4c0ff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0xb4c10000: 00 00 fa fa fa fa fa fa fa fa fa fa fa fa fa fa
^^ ^^
last valid first poisoned (redzones)
Remediation
Upgrade immediately to the patched Orthanc release superseding 1.12.10. Check the official Orthanc changelog and CERT/CC VU#536588 for the specific fixed version tag.
If immediate upgrade is not possible:
Network segmentation: Restrict port 8042 (REST) and 4242 (DICOM) to trusted PACS networks only. Orthanc should never be directly Internet-facing.
Enable Orthanc authentication: Set AuthenticationEnabled to true in orthanc.json and configure RegisteredUsers to prevent unauthenticated DICOM uploads.
Deploy upstream WAF rule to reject POST /instances bodies containing PAM headers with WIDTH or HEIGHT values whose product exceeds a safe threshold (e.g., 8192 × 8192 for most medical imaging use cases).
Run Orthanc under a memory-safe allocator (e.g., link against libasan in production temporarily, or deploy with scudo allocator) to convert exploitation into a deterministic crash rather than a silent corruption.