VYPR
High severityOSV Advisory· Published Jul 20, 2020· Updated Aug 4, 2024

CVE-2020-8215

CVE-2020-8215

Description

A buffer overflow in node-canvas <= 1.6.9 allows denial of service or code execution via crafted images.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A buffer overflow in node-canvas <= 1.6.9 allows denial of service or code execution via crafted images.

The vulnerability is a buffer overflow in the node-canvas library (versions ≤ 1.6.9) when processing user-supplied images. The root cause lies in the JPEG decoding routines (decodeJPEGIntoSurface, decodeJPEGBufferIntoMimeSurface, and loadJPEGFromBuffer), where the standard jpeg_error_mgr is used without a custom error handler, allowing malformed JPEG data to trigger an unhandled error condition and potentially overflow buffers [1][2].

Exploitation requires an attacker to provide a specially crafted image file to an application using the affected library. No prior authentication or special network position is necessary if the application processes user-uploaded images from untrusted sources. The attack surface is broad because node-canvas is widely used in web applications for server-side image manipulation.

An attacker can cause a denial of service (process crash) or potentially execute arbitrary code. The buffer overflow may corrupt memory in a way that allows an attacker to control program flow [1].

The fix, introduced in commit c3e4ccb1c404da01e83fe5eb3626bf55f7f55957, adds a custom JPEG error manager that uses setjmp/longjmp to safely recover from JPEG errors instead of crashing [2]. Users should upgrade to a patched version (≥ 1.6.10).

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
canvasnpm
< 1.6.111.6.11

Affected products

2

Patches

1
c3e4ccb1c404

Prevent JPEG errors from crashing process

https://github.com/Automattic/node-canvasLinus UnnebäckMar 20, 2018via ghsa
3 files changed · +90 5
  • src/Image.cc+55 3 modified
    @@ -19,6 +19,14 @@ typedef struct {
     } gif_data_t;
     #endif
     
    +#ifdef HAVE_JPEG
    +#include <setjmp.h>
    +
    +struct canvas_jpeg_error_mgr: jpeg_error_mgr {
    +  jmp_buf setjmp_buffer;
    +};
    +#endif
    +
     /*
      * Read closure used by loadFromBuffer.
      */
    @@ -752,6 +760,17 @@ Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args) {
       return CAIRO_STATUS_SUCCESS;
     }
     
    +/*
    + * Callback to recover from jpeg errors
    + */
    +
    +METHODDEF(void) canvas_jpeg_error_exit (j_common_ptr cinfo) {
    +  canvas_jpeg_error_mgr *cjerr = static_cast<canvas_jpeg_error_mgr*>(cinfo->err);
    +
    +  // Return control to the setjmp point
    +  longjmp(cjerr->setjmp_buffer, 1);
    +}
    +
     #if CAIRO_VERSION_MINOR >= 10
     
     /*
    @@ -764,8 +783,19 @@ Image::decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len) {
       // TODO: remove this duplicate logic
       // JPEG setup
       struct jpeg_decompress_struct args;
    -  struct jpeg_error_mgr err;
    +  struct canvas_jpeg_error_mgr err;
    +
       args.err = jpeg_std_error(&err);
    +  args.err->error_exit = canvas_jpeg_error_exit;
    +
    +  // Establish the setjmp return context for canvas_jpeg_error_exit to use
    +  if (setjmp(err.setjmp_buffer)) {
    +    // If we get here, the JPEG code has signaled an error.
    +    // We need to clean up the JPEG object, close the input file, and return.
    +    jpeg_destroy_decompress(&args);
    +    return CAIRO_STATUS_READ_ERROR;
    +  }
    +
       jpeg_create_decompress(&args);
     
       jpeg_mem_src(&args, buf, len);
    @@ -858,8 +888,19 @@ Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) {
       // TODO: remove this duplicate logic
       // JPEG setup
       struct jpeg_decompress_struct args;
    -  struct jpeg_error_mgr err;
    +  struct canvas_jpeg_error_mgr err;
    +
       args.err = jpeg_std_error(&err);
    +  args.err->error_exit = canvas_jpeg_error_exit;
    +
    +  // Establish the setjmp return context for canvas_jpeg_error_exit to use
    +  if (setjmp(err.setjmp_buffer)) {
    +    // If we get here, the JPEG code has signaled an error.
    +    // We need to clean up the JPEG object, close the input file, and return.
    +    jpeg_destroy_decompress(&args);
    +    return CAIRO_STATUS_READ_ERROR;
    +  }
    +
       jpeg_create_decompress(&args);
     
       jpeg_mem_src(&args, buf, len);
    @@ -883,8 +924,19 @@ Image::loadJPEG(FILE *stream) {
       if (data_mode == DATA_IMAGE) { // Can lazily read in the JPEG.
         // JPEG setup
         struct jpeg_decompress_struct args;
    -    struct jpeg_error_mgr err;
    +    struct canvas_jpeg_error_mgr err;
    +
         args.err = jpeg_std_error(&err);
    +    args.err->error_exit = canvas_jpeg_error_exit;
    +
    +    // Establish the setjmp return context for canvas_jpeg_error_exit to use
    +    if (setjmp(err.setjmp_buffer)) {
    +      // If we get here, the JPEG code has signaled an error.
    +      // We need to clean up the JPEG object, close the input file, and return.
    +      jpeg_destroy_decompress(&args);
    +      return CAIRO_STATUS_READ_ERROR;
    +    }
    +
         jpeg_create_decompress(&args);
     
         jpeg_stdio_src(&args, stream);
    
  • test/fixtures/chrome.jpg+0 0 added
  • test/image.test.js+35 2 modified
    @@ -5,10 +5,12 @@
     
     var Canvas = require('../')
       , Image = Canvas.Image
    -  , assert = require('assert');
    +  , assert = require('assert')
    +  , fs = require('fs');
     
     var png_checkers = __dirname + '/fixtures/checkers.png';
     var png_clock = __dirname + '/fixtures/clock.png';
    +var jpg_chrome = __dirname + '/fixtures/chrome.jpg'
     
     describe('Image', function () {
       it('should require new', function () {
    @@ -206,4 +208,35 @@ describe('Image', function () {
         assert.equal(img.src, png_clock + 's3');
         assert.equal(onerrorCalled, 0);
       });
    -});
    +
    +  it('does not crash on invalid images', function () {
    +    function tryImage (src) {
    +      var img = new Image()
    +      img.src = src
    +      // if we came this far we didn't crash!
    +    }
    +
    +    function withIncreasedByte (source, index) {
    +      var copy = source.slice(0)
    +
    +      copy[index] += 1
    +
    +      return copy
    +    }
    +
    +    var source = fs.readFileSync(jpg_chrome)
    +
    +    tryImage(withIncreasedByte(source, 0))
    +    tryImage(withIncreasedByte(source, 1))
    +    tryImage(withIncreasedByte(source, 1060))
    +    tryImage(withIncreasedByte(source, 1061))
    +    tryImage(withIncreasedByte(source, 1062))
    +    tryImage(withIncreasedByte(source, 1063))
    +    tryImage(withIncreasedByte(source, 1064))
    +    tryImage(withIncreasedByte(source, 1065))
    +    tryImage(withIncreasedByte(source, 1066))
    +    tryImage(withIncreasedByte(source, 1067))
    +    tryImage(withIncreasedByte(source, 1068))
    +    tryImage(withIncreasedByte(source, 1069))
    +  })
    +})
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.