home intel cve-2026-5444-orthanc-pam-heap-overflow
CVE Analysis 2026-04-09 · 8 min read

CVE-2026-5444: Orthanc PAM Parser Integer Overflow → Heap Buffer Overflow

32-bit integer overflow in Orthanc's PAM image dimension calculation allocates an undersized heap buffer, enabling a controlled heap write during pixel processing of crafted DICOM files.

#heap-buffer-overflow#integer-overflow#dicom-parsing#pam-image-processing#arbitrary-code-execution
Technical mode — for security professionals
▶ Attack flow — CVE-2026-5444 · Buffer Overflow
ATTACKERRemote / unauthBUFFER OVERFLOWCVE-2026-5444Android · HIGHCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

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:

// Orthanc image codec — PAM decoder (DecodePamImage)
// Reconstructed from binary + advisory description

typedef struct {
    uint32_t width;
    uint32_t height;
    uint32_t depth;       // channel count
    uint32_t maxval;      // max sample value, determines bytes_per_sample
} PamHeader;

static OrthancPluginErrorCode DecodePamImage(
        const void   *compressed,
        uint64_t      compressedSize,
        void        **decodedBuffer,
        uint64_t     *decodedSize)
{
    PamHeader hdr;
    ParsePamHeader(compressed, compressedSize, &hdr);   // reads ASCII fields

    uint32_t bytes_per_sample = (hdr.maxval > 255) ? 2 : 1;

    // BUG: all operands are uint32_t; product silently wraps on overflow
    uint32_t bufSize = hdr.width * hdr.height * hdr.depth * bytes_per_sample;

    uint8_t *pixelBuf = (uint8_t *)malloc(bufSize);     // undersized allocation
    if (!pixelBuf) return ORTHANC_PLUGIN_ERROR_NOT_ENOUGH_MEMORY;

    // pixel copy uses the raw (large) parsed dimensions — not bufSize
    // BUG: writes width*height*depth*bytes_per_sample bytes regardless of bufSize
    CopyPamPixels(pixelBuf, compressed, compressedSize, &hdr);  // heap overflow here
    *decodedBuffer = pixelBuf;
    *decodedSize   = bufSize;   // reported size is also wrong (wrapped value)
    return ORTHANC_PLUGIN_SUCCESS;
}

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.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →