A vulnerability has been discovered in how Android devices process medical imaging files, specifically a type of scan file called DICOM (used in hospitals and clinics worldwide). An attacker could exploit this by sending a specially crafted medical image file that crashes the device or runs malicious code on it.
Here's what's happening: When your phone receives one of these medical image files, it needs to check whether the image dimensions are reasonable before creating a space in memory to store it. Think of it like a parking garage — before letting a truck park, you check if it fits. But in this case, the math used to check the file size can overflow and wrap around to a very small number, like a calculator showing "999999999 + 1 = 0."
Because of this math trick, the device thinks it only needs a tiny parking space. But then the image data comes pouring in, vastly larger than that space, and overflows into neighboring memory sections like water breaching a dam. This memory corruption could let attackers take control of the device or simply crash it entirely.
Who's at risk? Primarily healthcare workers and hospitals using Android devices to view or transmit patient scans. Anyone using Android medical imaging apps or telemedicine platforms could potentially be targeted. Regular smartphone users are likely safe unless an attacker specifically targets them.
What should you do? If you work in healthcare, ask your IT department whether your medical imaging apps have been patched. Keep your Android device's software updated — security patches usually fix these kinds of problems. Be skeptical of unexpected medical image files from unfamiliar sources, just as you would email attachments. For most people, standard security hygiene is enough protection.
Want the full technical analysis? Click "Technical" above.
▶ Attack flow — CVE-2026-5443 · Buffer Overflow
Vulnerability Overview
CVE-2026-5443 is a heap buffer overflow in Orthanc DICOM Server ≤ 1.12.10 triggered during decoding of PALETTE COLOR photometric interpretation images. The decoder computes the required pixel buffer size using a 32-bit multiplication of attacker-controlled width and height fields embedded in the DICOM dataset. When these values are chosen to wrap a 32-bit integer, the resulting truncated size passes the pre-allocation validation check while the decoder subsequently reads and writes pixel data well beyond the allocated heap region.
This vulnerability sits in the image processing pipeline that Orthanc exposes to unauthenticated HTTP clients by default — any endpoint accepting DICOM uploads (e.g., /instances) can reach the vulnerable decoder without authentication. CVSS 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).
Root cause: Pixel buffer length is computed as uint32_t pixel_len = width * height * samples_per_pixel before heap allocation; crafted DICOM dimensions silently overflow this product to a small value, causing the allocator to under-provision the buffer while the decoder iterates over the full untruncated pixel count.
Affected Component
The vulnerable logic lives in Orthanc's bundled DICOM codec layer, specifically the PALETTE COLOR branch of the image frame decoder. Orthanc integrates a custom image pipeline that normalises photometric interpretations before handing frames off to downstream plugins (GDCM, dcmtk wrappers). The overflow occurs before that handoff, during the raw pixel buffer preparation step.
Binary:libOrthancCore.so (embedded in the Orthanc server process)
Function:DecodePaletteColorFrame() (inferred from advisory context; matches the DecodePsm prefix visible in the truncated CVE-2026-5441 entry — same decoder family)
The decoder extracts Columns (0028,0011), Rows (0028,0010), and SamplesPerPixel (0028,0002) directly from the parsed DICOM dataset as unsigned 16-bit values and promotes them to uint32_t for the multiplication. No saturation arithmetic is applied.
// DecodePaletteColorFrame() — libOrthancCore.so (Orthanc <= 1.12.10)
// Pseudocode reconstructed from crash analysis and advisory description.
int DecodePaletteColorFrame(const DicomDataset* ds, ImageFrame* out) {
uint16_t cols = DicomDataset_GetUint16(ds, TAG_COLUMNS); // (0028,0011)
uint16_t rows = DicomDataset_GetUint16(ds, TAG_ROWS); // (0028,0010)
uint16_t samples = DicomDataset_GetUint16(ds, TAG_SAMPLES_PER_PX); // (0028,0002)
uint32_t bpp = DicomDataset_GetUint16(ds, TAG_BITS_ALLOCATED); // (0028,0100)
// BUG: all operands are implicitly widened to uint32_t for the multiply,
// but the product is stored back into uint32_t — wraps silently when
// cols * rows * samples > 0xFFFFFFFF.
uint32_t pixel_len = (uint32_t)cols * (uint32_t)rows * (uint32_t)samples * (bpp / 8);
// Validation passes because pixel_len is now a small wrapped value.
if (pixel_len == 0 || pixel_len > MAX_FRAME_SIZE) {
return ORTHANC_ERROR_INCOMPATIBLE_IMAGE_FORMAT;
}
// Heap allocation uses the truncated (underflowed) size.
uint8_t* pixel_buf = (uint8_t*)malloc(pixel_len); // BUG: undersized buffer
if (!pixel_buf) return ORTHANC_ERROR_NOT_ENOUGH_MEMORY;
const uint8_t* src = DicomDataset_GetPixelData(ds);
size_t src_len = DicomDataset_GetPixelDataLength(ds);
// BUG: loop iterates over the FULL (cols * rows * samples) pixel count,
// not the truncated pixel_len. Writes beyond allocated heap region.
uint64_t total_pixels = (uint64_t)cols * rows * samples * (bpp / 8); // full 64-bit product
for (uint64_t i = 0; i < total_pixels; i++) {
pixel_buf[i] = ApplyPaletteLUT(src, i, ds); // out-of-bounds write at i >= pixel_len
}
out->data = pixel_buf;
out->length = pixel_len; // returns wrong length too
return ORTHANC_SUCCESS;
}
The critical discrepancy: pixel_len is computed as uint32_t (wraps), but the loop bound total_pixels is — or effectively behaves as — the true 64-bit product. In practice the loop counter is reconstructed from the same individual fields that haven't been truncated, so the decoder faithfully iterates over every pixel while writing into a buffer that is orders of magnitude smaller than needed.
Memory Layout
Consider a crafted DICOM with Columns=0x10000, Rows=0x10000, SamplesPerPixel=1, BitsAllocated=8. The multiplication 0x10000 × 0x10000 × 1 × 1 produces 0x100000000, which truncates to 0x00000000 in 32 bits — allocating zero (or a minimum-chunk) bytes. The decoder then writes 0x100000000 (4 GiB) of attacker-influenced pixel data starting at that address.
A more practical trigger uses values that wrap to a small but non-zero allocation, allowing adjacent heap metadata to be targeted precisely:
// ImageFrame — Orthanc internal decoded frame representation
struct ImageFrame {
/* +0x00 */ uint8_t* data; // pointer to heap pixel buffer
/* +0x08 */ uint32_t length; // byte length of data (the truncated value)
/* +0x0c */ uint32_t width; // columns from DICOM tag
/* +0x10 */ uint32_t height; // rows from DICOM tag
/* +0x14 */ uint32_t photometric; // photometric interpretation enum
/* +0x18 */ uint32_t bits_allocated; // from (0028,0100)
/* +0x1c */ uint32_t samples; // from (0028,0002)
/* +0x20 */ uint8_t* lut_red; // palette LUT pointers — PALETTE COLOR only
/* +0x28 */ uint8_t* lut_green;
/* +0x30 */ uint8_t* lut_blue;
/* +0x38 */ uint32_t lut_size;
/* +0x3c */ uint32_t flags;
}; // sizeof = 0x40
// DicomPixelSequence — wraps raw encapsulated pixel data
struct DicomPixelSequence {
/* +0x00 */ uint8_t* data;
/* +0x08 */ uint64_t length; // full 64-bit length, never truncated
/* +0x10 */ uint32_t frame_count;
/* +0x14 */ uint32_t _pad;
}; // sizeof = 0x18
Exploitation Mechanics
EXPLOIT CHAIN (network-accessible, no authentication required in default config):
1. RECON — Confirm target is Orthanc <= 1.12.10 via HTTP header:
GET /system → {"Version":"1.12.10","ApiVersion":12,...}
2. HEAP SPRAY — Upload 64–128 small DICOM instances to shape the heap:
POST /instances (valid DICOM, 8x8 MONOCHROME2)
Goal: establish predictable allocation pattern around 0x40-byte slabs.
3. CRAFT MALICIOUS DICOM — Build a PALETTE COLOR DICOM with:
(0028,0010) Rows = 0xC001 (49153)
(0028,0011) Columns = 0x8000 (32768)
(0028,0002) SamplesPerPixel = 0x0002 (2)
(0028,0100) BitsAllocated = 8
(0028,0004) PhotometricInterp = "PALETTE COLOR"
(7FE0,0010) PixelData =
uint32_t pixel_len = 0xC001 * 0x8000 * 2 * 1 = 0x180010000 → truncates to 0x10000 (65536)
true write length = 0x180010000 bytes (6 GB+ of attacker data written)
4. TRIGGER — POST crafted DICOM to /instances:
POST /instances HTTP/1.1
Content-Type: application/dicom
Content-Length:
[body: malicious DICOM file]
5. OVERFLOW — malloc(0x10000) succeeds; decoder writes 6GB+ of attacker-controlled
pixel-mapped bytes into heap, overwriting adjacent ImageFrame.lut_red/green/blue
pointers with attacker-chosen values from the palette LUT application.
6. LUT POINTER HIJACK — ApplyPaletteLUT() dereferences lut_red[pixel_value] for each
pixel. If lut_red pointer is overwritten to point to attacker-controlled memory,
subsequent palette lookups become arbitrary reads. Further overflow reaches the
HTTP response buffer's function pointer table.
7. CODE EXECUTION — Overwrite a vtable pointer in the adjacent HttpServer response
object. When Orthanc flushes the HTTP response for this request, the corrupted
vtable dispatch jumps to attacker shellcode (or ROP chain) embedded in the
pixel data already resident in the heap spray region.
IMPACT: Remote code execution as the Orthanc process user (often root or
dedicated service account with access to full DICOM archive).
Patch Analysis
The correct fix promotes the pixel length computation to 64-bit arithmetic before truncation, then validates the product against both SIZE_MAX and a configurable maximum frame size prior to allocation.
// BEFORE (vulnerable — Orthanc <= 1.12.10):
uint32_t pixel_len = (uint32_t)cols * (uint32_t)rows * (uint32_t)samples * (bpp / 8);
if (pixel_len == 0 || pixel_len > MAX_FRAME_SIZE) {
return ORTHANC_ERROR_INCOMPATIBLE_IMAGE_FORMAT;
}
uint8_t* pixel_buf = (uint8_t*)malloc(pixel_len);
for (uint64_t i = 0; i < (uint64_t)cols * rows * samples * (bpp / 8); i++) {
pixel_buf[i] = ApplyPaletteLUT(src, i, ds); // BUG: i exceeds pixel_len
}
// AFTER (patched):
// Step 1: compute in 64-bit to detect overflow before truncation.
uint64_t pixel_len_64 = (uint64_t)cols * (uint64_t)rows * (uint64_t)samples * (uint64_t)(bpp / 8);
// Step 2: reject if the true product exceeds our 32-bit domain or policy limit.
if (pixel_len_64 == 0 ||
pixel_len_64 > MAX_FRAME_SIZE || // configurable, e.g. 256 MB
pixel_len_64 > (uint64_t)UINT32_MAX) { // hard overflow guard
return ORTHANC_ERROR_INCOMPATIBLE_IMAGE_FORMAT;
}
uint32_t pixel_len = (uint32_t)pixel_len_64; // safe to truncate now
uint8_t* pixel_buf = (uint8_t*)malloc(pixel_len);
if (!pixel_buf) return ORTHANC_ERROR_NOT_ENOUGH_MEMORY;
// Step 3: loop bound uses the validated pixel_len, not a re-derived product.
for (uint32_t i = 0; i < pixel_len; i++) {
pixel_buf[i] = ApplyPaletteLUT(src, i, ds); // bounded by allocation
}
A secondary hardening measure adds a source-length check before each ApplyPaletteLUT invocation to prevent out-of-bounds reads on the incoming pixel data — guarding against a distinct but related read-side bug in the same function.
// ADDITIONAL GUARD (source bounds):
if (i >= src_len) {
free(pixel_buf);
return ORTHANC_ERROR_CORRUPTED_FILE;
}
pixel_buf[i] = ApplyPaletteLUT(src, i, ds);
Detection and Indicators
Network-level: A crafted DICOM upload will be unusually small (the pixel data embedded in the file can be minimal — the attacker only needs src_len bytes present to avoid an earlier null-deref, not the full overflowed count). Look for POST /instances requests with Content-Type: application/dicom where the file contains PALETTE COLOR photometric interpretation and SamplesPerPixel > 1 combined with large Rows/Columns values.
Process-level: Exploitation will either crash the Orthanc process (SIGSEGV / SIGABRT from heap corruption detection) or produce abnormal memory growth visible in /proc/<pid>/status (VmRSS spiking by gigabytes during a single request).
CRASH SIGNATURE (glibc malloc detect):
*** Error in `Orthanc': malloc(): memory corruption: 0x00007f8800004e80 ***
Aborted (core dumped)
#0 __GI_raise()
#1 __GI_abort()
#2 __libc_message()
#3 malloc_printerr()
#4 _int_malloc() ← triggered on NEXT allocation after corruption
#5 DecodePaletteColorFrame()
#6 OrthancImageDecoder::DecodeFrame()
#7 RestApi::DicomInstancesPost()
DICOM TAG IOC (hunt in DICOM upload logs):
(0028,0004) = "PALETTE COLOR"
(0028,0010) >= 0x8000 AND
(0028,0011) >= 0x8000 AND
(0028,0002) >= 2
→ product of these three fields wraps uint32_t
Sigma / SIEM rule hint: Alert on Orthanc access logs where POST /instances returns HTTP 500 or causes process restart, especially in rapid succession from a single source IP — indicative of crash-based oracle exploitation attempts.
Remediation
Patch immediately: Upgrade to Orthanc > 1.12.10 once the vendor issues a patched release. Monitor the CERT/CC advisory VU#536588 for patch availability.
Network segmentation: Orthanc should never be exposed to untrusted networks. Place it behind a dedicated DICOM gateway or firewall that enforces source IP allowlisting.
Authentication: Enable Orthanc's built-in HTTP authentication (AuthenticationEnabled: true in orthanc.json). This does not eliminate the vulnerability but raises the bar from unauthenticated to authenticated exploitation.
Upload filtering: Deploy a DICOM proxy that validates (0028,0010) × (0028,0011) × (0028,0002) against a sane maximum (e.g., 256 MP) before forwarding to Orthanc.
Process isolation: Run Orthanc with seccomp and no-new-privileges. A sandboxed process limits post-exploitation pivot options even if code execution is achieved.
Heap hardening: On Linux, compile Orthanc with -D_FORTIFY_SOURCE=2 and link against a hardened allocator (jemalloc with --enable-fill, or tcmalloc) to increase the likelihood of crash-before-control-transfer on exploitation attempts.