highSep 8, 2026

CVE-2026-84383: sharp Heap Buffer Overflow via Crafted AVIF/HEIC File (libheif RCE)

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Passing a crafted AVIF or HEIC image to any version of sharp before 0.35.4 can trigger a heap buffer overflow inside libheif and lead to remote code execution on Linux.

Packagesharp
Ecosystemnpm
Affected< 0.35.4
Fixed in0.35.4
CVE-2026-84383: sharp Heap Buffer Overflow via Crafted AVIF/HEIC File (libheif RCE)

The problem

sharp bundles libheif for AVIF and HEIC decoding. Versions of libheif through 1.23.1 contain two critical bugs (CVE-2026-84383 / GHSA-g89c-p67h-r497 and GHSA-2jg2-4ch7-h545) in its derived-image handling code.

The primary bug is a heap buffer overflow in scale_nearest_neighbor(). A crafted file encodes nested iden (identity-derivation) and auxl (auxiliary) item references that cause libheif to build a decoded image with two Alpha plane entries at different bit depths.

The scaler allocates a destination buffer sized for the 8-bit entry but writes 16-bit samples from the second entry into it, overflowing roughly 16,384 bytes past the allocation. Both the overflow length and the written values are attacker-controlled.

The second bug (GHSA-2jg2-4ch7-h545) is an OOB read/write in crop, scale, and plane-extraction code that indexes pixel planes with the wrong geometry when the file attaches planes whose declared size differs from the image dimensions, also via iden/auxl chains.

A working RCE exploit for this path was confirmed by the researchers.

Proof of concept

A working proof-of-concept for this issue in sharp, with the exact payload below.

javascript
// Attacker serves a crafted AVIF to any endpoint that passes user-supplied
// files through sharp (e.g. an upload handler or image proxy):
const sharp = require('sharp');

// crafted.avif contains nested iden/auxl items with mismatched Alpha
// bit-depths (8-bit alloc, 16-bit write) as generated by gen_poc.py
// published in the upstream libheif advisory GHSA-g89c-p67h-r497.
sharp('crafted.avif')
  .resize(256, 256)
  .toBuffer();
// -> triggers HeifPixelImage::scale_nearest_neighbor() OOB write
// -> ~16 384 bytes of attacker-controlled heap corruption
// -> RCE on glibc Linux when node binary is NOT compiled as PIE

// Immediate workaround (pre-patch):
sharp.block({ operation: ['VipsForeignLoadHeif'] });

The root cause is that HeifPixelImage::transfer_channel_from_image_as() allows duplicate destination channels, so a second Alpha plane with a different bit depth is silently appended rather than rejected. When the image is later scaled, scale_nearest_neighbor() picks up the allocation size from the first (8-bit) plane descriptor but iterates using the sample width of the second (16-bit) plane, writing twice as many bytes as the buffer holds.

The patch commit f4fb8bde47 (libheif v1.23.2) adds bit-depth and size consistency checks at every point where planes are consumed: in the scaler, the cropper, and the plane-extraction paths. The second advisory (GHSA-2jg2-4ch7-h545) is fixed by the same release by validating that all pixel-plane dimensions match the declared image geometry before indexing.

The fix

Upgrade sharp to 0.35.4 or later. That release ships libheif 1.23.2 in its prebuilt binaries, which includes the fix commit f4fb8bde47. If you use a globally-installed libheif instead of sharp's prebuilt binaries, upgrade that to 1.23.2 directly. As an immediate workaround before upgrading, add sharp.block({ operation: ['VipsForeignLoadHeif'] }) at application startup to prevent sharp from decoding any HEIF/AVIF input.

Note: RCE is significantly harder (but not impossible) when the node binary is compiled as a Position Independent Executable (PIE); official Node.js binaries are NOT PIE by default.

Reported by Harsh Jaiswal (@rootxharsh) and Karim Rahal (@KarimPwnz).

References: [1][2][3][4][5]

Related research