From c3e4ccb1c404da01e83fe5eb3626bf55f7f55957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Unneb=C3=A4ck?= Date: Tue, 20 Mar 2018 15:50:48 +0000 Subject: [PATCH] Prevent JPEG errors from crashing process --- src/Image.cc | 58 +++++++++++++++++++++++++++++++++++++-- test/fixtures/chrome.jpg | Bin 0 -> 5750 bytes test/image.test.js | 37 +++++++++++++++++++++++-- 3 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 test/fixtures/chrome.jpg diff --git a/src/Image.cc b/src/Image.cc index 0147f878f..6b957fa8a 100644 --- a/src/Image.cc +++ b/src/Image.cc @@ -19,6 +19,14 @@ typedef struct { } gif_data_t; #endif +#ifdef HAVE_JPEG +#include + +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(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); diff --git a/test/fixtures/chrome.jpg b/test/fixtures/chrome.jpg new file mode 100644 index 0000000000000000000000000000000000000000..29fd36ae808b6f80dd97050474c5f581bed07e80 GIT binary patch literal 5750 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 N?EjB{Eg<{v{{i+a-=F{h literal 0 HcmV?d00001 diff --git a/test/image.test.js b/test/image.test.js index ae39d6207..8f7116f6e 100644 --- a/test/image.test.js +++ b/test/image.test.js @@ -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)) + }) +})