I am reporting a native memory corruption issue in node-canvas.
In node-canvas v3.2.3, loading a crafted JPEG from a Node.js Buffer can trigger a signed integer overflow in the JPEG decode path. The overflow causes node-canvas to allocate a buffer that is much smaller than the decoded image requires. The decoder then writes decoded pixels into that undersized buffer, causing a heap buffer overflow.
AddressSanitizer confirms a heap-buffer-overflow, and debugger testing confirms that the write continues into adjacent heap memory. In non-sanitized runs, the overwrite reaches adjacent heap chunk metadata and corrupts neighboring heap-resident libjpeg controller state, later used by an indirect call during normal JPEG decoding.
The issue is in the JPEG Buffer input path. The file input path has a size check that rejects oversized JPEG dimensions, but the Buffer path reaches the unsafe decode logic.
Code Locations
src/Image.cc, Image::loadJPEGFromBuffer()
src/Image.cc, Image::decodeJPEGIntoSurface()
src/Image.cc, Image::jpegToARGB()
Root Cause
The vulnerable allocation is:
uint8_t *data = new uint8_t[naturalWidth * naturalHeight * channels];
For a JPEG with decoded dimensions 32768 x 32768, the intended ARGB output size is 32768 * 32768 * 4, which is 0x100000000 bytes. In the current code path this calculation is performed with signed integer arithmetic and overflows. The pixel-copy loop then writes as if the full image-sized buffer had been allocated.
Relevant CWE IDs
CWE-122: Heap-based Buffer Overflow
CWE-787: Out-of-bounds Write
CWE-190: Integer Overflow or Wraparound
CWE-131: Incorrect Calculation of Buffer Size
CWE-680: Integer Overflow to Buffer Overflow, as a chain description
Impact
Applications that accept attacker-controlled image bytes and pass them to node-canvas as a Buffer, for example through loadImage(body) or Image.src = buffer, may be exposed to native process crashes and memory corruption. The current evidence supports treating this as more than denial of service, because corrupted heap state is later consumed by an indirect call.
Expected Behavior
node-canvas should reject JPEGs whose decoded dimensions or decoded byte size exceed safe limits before allocating or decoding. The Buffer path should enforce the same kind of bounds as the file path. Size calculations should use checked size_t arithmetic and fail safely on overflow.
Happy to send the PR with the patch.
I am reporting a native memory corruption issue in
node-canvas.In
node-canvas v3.2.3, loading a crafted JPEG from a Node.jsBuffercan trigger a signed integer overflow in the JPEG decode path. The overflow causesnode-canvasto allocate a buffer that is much smaller than the decoded image requires. The decoder then writes decoded pixels into that undersized buffer, causing a heap buffer overflow.AddressSanitizer confirms a heap-buffer-overflow, and debugger testing confirms that the write continues into adjacent heap memory. In non-sanitized runs, the overwrite reaches adjacent heap chunk metadata and corrupts neighboring heap-resident libjpeg controller state, later used by an indirect call during normal JPEG decoding.
The issue is in the JPEG
Bufferinput path. The file input path has a size check that rejects oversized JPEG dimensions, but theBufferpath reaches the unsafe decode logic.Code Locations
src/Image.cc,Image::loadJPEGFromBuffer()src/Image.cc,Image::decodeJPEGIntoSurface()src/Image.cc,Image::jpegToARGB()Root Cause
The vulnerable allocation is:
For a JPEG with decoded dimensions
32768 x 32768, the intended ARGB output size is32768 * 32768 * 4, which is0x100000000bytes. In the current code path this calculation is performed with signed integer arithmetic and overflows. The pixel-copy loop then writes as if the full image-sized buffer had been allocated.Relevant CWE IDs
CWE-122: Heap-based Buffer OverflowCWE-787: Out-of-bounds WriteCWE-190: Integer Overflow or WraparoundCWE-131: Incorrect Calculation of Buffer SizeCWE-680: Integer Overflow to Buffer Overflow, as a chain descriptionImpact
Applications that accept attacker-controlled image bytes and pass them to
node-canvasas aBuffer, for example throughloadImage(body)orImage.src = buffer, may be exposed to native process crashes and memory corruption. The current evidence supports treating this as more than denial of service, because corrupted heap state is later consumed by an indirect call.Expected Behavior
node-canvasshould reject JPEGs whose decoded dimensions or decoded byte size exceed safe limits before allocating or decoding. TheBufferpath should enforce the same kind of bounds as the file path. Size calculations should use checkedsize_tarithmetic and fail safely on overflow.Happy to send the PR with the patch.