Description
Issue
Certain calls to getImageData
can cause node to crash. It looks like it's rectangles that are completely outside of the canvas cause this, but I'm not completely sure.
I have a suspicion that this is the same issue as #1749, but hopefully I'll have some more information for you all to go on, and this is also somewhat related to the fact that this behavior isn't spec compliant (#1849).
Steps to Reproduce
const { createCanvas, loadImage } = require('canvas')
const canvas = createCanvas(200, 200)
const ctx = canvas.getContext('2d')
ctx.rect(0, 0, 100, 100);
ctx.fill();
let imageData = ctx.getImageData(0, -11, 10, 10);
Running the above will get us a core dump with a FATAL ERROR: v8::ArrayBuffer::New Allocation failed - process out of memory
error message, however if you change that call to something like getImageData(0, -9, 10, 10)
, it runs fine (both seem to work without errors in browsers). This doesn't seem to be a genuine out of memory error, however. A quick trip to lldb
gets us a stack trace that looks something like this:
(llnode) v8 bt
* thread #1: tid = 16692, 0x00007f7be531f03b libc.so.6`__GI_raise(sig=2) at raise.c:51:1, name = 'node', stop reason = signal SIGABRT
* frame #0: 0x00007f7be531f03b libc.so.6`__GI_raise(sig=2) at raise.c:51:1
frame #1: 0x00007f7be52fe859 libc.so.6`__GI_abort at abort.c:79:7
frame #2: 0x0000000000a1ae61 node`node::Abort() + 33
frame #3: 0x0000000000a1b25c node`node::OnFatalError(char const*, char const*) + 172
frame #4: 0x0000000000b9b20e node`v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) + 78
frame #5: 0x0000000000b9b589 node`v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) + 841
frame #6: 0x0000000000b9b66b node`v8::internal::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*) + 11
frame #7: 0x0000000000bb943e node`v8::ArrayBuffer::New(v8::Isolate*, unsigned long) + 270
frame #8: 0x00007f7be4294f7e canvas.node`Context2d::GetImageData(info=0x00007ffc4097c760) at CanvasRenderingContext2d.cc:1018:75
frame #9: 0x00007f7be42751de canvas.node`Nan::imp::FunctionCallbackWrapper(info=0x00007ffc4097c7e0) at nan_callbacks_12_inl.h:176:11
frame #10: 0x0000000000c07179 node`v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) + 457
frame #11: 0x0000000000c08f67 node`v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) + 183
frame #12: 0x000000000140e919 node`Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit at base.tq:3418
frame #13: 0x00000000013940a4 (anonymous)(this=0x2a28f3582ab1:<Object: Object>, 0x2a28f3582ab1:<Object: Object>, 0x2a28f35829d1:<function: require at internal/modules/cjs/helpers.js:1:10>, 0x2a28f3582979:<Object: Module>, 0x7c5cf2e2ab1:<String: "/home/nvladimiro...">, 0x2a28f3582a91:<String: "/home/nvladimiro...">) at /home/nvladimiroff/code/test_node/index.js:1:0 fn=0x00002a28f3582a51
frame #14: 0x00000000013940a4 (this=0x2a28f3582979:<Object: Module>, 0x2a28f3582ae9:<String: "const { createCa...">, 0x7c5cf2e2ab1:<String: "/home/nvladimiro...">) at internal/modules/cjs/loader.js:1:10 fn=0x000007c5cf2de581
frame #15: 0x00000000013940a4 Module._extensions..js(this=0x2a28f3580939:<Object: Object>, 0x2a28f3582979:<Object: Module>, 0x7c5cf2e2ab1:<String: "/home/nvladimiro...">) at internal/modules/cjs/loader.js:1:10 fn=0x000007c5cf2de5d9
frame #16: 0x00000000013940a4 (this=0x2a28f3582979:<Object: Module>, 0x7c5cf2e2ab1:<String: "/home/nvladimiro...">) at internal/modules/cjs/loader.js:1:10 fn=0x000007c5cf2de4d1
frame #17: 0x00000000013940a4 Module._load(this=0x1fae2b9c0189:<function: Module at internal/modules/cjs/loader.js:1:10>, 0x2a28f3582c21:<String: "/home/nvladimiro...">, 0xc9bd82001b9:<null>, 0xc9bd8200639:<true>) at internal/modules/cjs/loader.js:1:10 fn=0x000007c5cf2de3c1
frame #18: 0x00000000013940a4 executeUserEntryPoint(this=0x1fae2b9c0189:<function: Module at internal/modules/cjs/loader.js:1:10>, 0x2a28f3582c21:<String: "/home/nvladimiro...">) at internal/modules/run_main.js:1:10 fn=0x00002a28f3582c61
frame #19: 0x00000000013940a4 (anonymous)(this=0xc9bd82004b1:<undefined>, 0x1fae2b9c06a1:<Object: process>, 0x1fae2b9c0661:<function: nativeModuleRequire at internal/bootstrap/loaders.js:1:10>, 0x1fae2b9c0621:<function: internalBinding at internal/bootstrap/loaders.js:1:10>, 0x62a274cd5d9:<Object: Object>, 0xae8fbfe6cd1:<function: at >) at internal/main/run_main_module.js:1:0 fn=0x00001fae2b9c05e1
frame #20: 0x000000000139161d <internal>
frame #21: 0x00000000013913f8 <entry>
frame #22: 0x0000000000ced5b0 node`v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) + 432
frame #23: 0x0000000000ceda68 node`v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*) + 88
frame #24: 0x0000000000baceab node`v8::Function::Call(v8::Local<v8::Context>, v8::Local<v8::Value>, int, v8::Local<v8::Value>*) + 363
frame #25: 0x00000000009e7451 node`node::ExecuteBootstrapper(node::Environment*, char const*, std::vector<v8::Local<v8::String>, std::allocator<v8::Local<v8::String> > >*, std::vector<v8::Local<v8::Value>, std::allocator<v8::Local<v8::Value> > >*) + 113
frame #26: 0x00000000009e7766 node`node::StartExecution(node::Environment*, char const*) + 534
frame #27: 0x00000000009e8b28 node`node::StartExecution(node::Environment*, std::function<v8::MaybeLocal<v8::Value> (node::StartExecutionCallbackInfo const&)>) + 1384
frame #28: 0x0000000000988f01 node`node::LoadEnvironment(node::Environment*) + 97
frame #29: 0x0000000000a5de94 node`node::NodeMainInstance::Run() + 340
frame #30: 0x00000000009eb1ec node`node::Start(int, char**) + 684
frame #31: 0x00007f7be53000b3 libc.so.6`__libc_start_main(main=(node`main), argc=2, argv=0x00007ffc4097d748, init=<unavailable>, fini=<unavailable>, rtld_fini=<unavailable>, stack_end=0x00007ffc4097d738) at libc-start.c:308:16
frame #32: 0x0000000000982005 node`_start + 41
The interesting line being on frame 8:
(llnode) frame select 8
frame #8: 0x00007f7be4294f7e canvas.node`Context2d::GetImageData(info=0x00007ffc4097c760) at CanvasRenderingContext2d.cc:1018:75
1015
1016 uint8_t *src = canvas->data();
1017
-> 1018 Local<ArrayBuffer> buffer = ArrayBuffer::New(Isolate::GetCurrent(), size);
1019 Local<TypedArray> dataArray;
1020
1021 if (canvas->backend()->getFormat() == CAIRO_FORMAT_RGB16_565) {
That call to ArrayBuffer::New
is ultimately the one causing the core dump (it looks to be coming from here). If we take a look at the current local variables, we get:
(llnode) frame variable
(Nan::NAN_METHOD_ARGS_TYPE) info = 0x00007ffc4097c760: {
info_ = 0x00007ffc4097c7e0
data_ = (val_ = 0x0000000004415430)
}
(Context2d *) context = 0x0000000004464b70
(Canvas *) canvas = 0x0000000004453db0
(int) sx = 0
(int) sy = 0
(int) sw = 10
(int) sh = -1
(int) width = 200
(int) height = 200
(int) srcStride = 800
(int) bpp = 4
(int) size = -40
(int) dstStride = 40
(uint8_t *) src = 0x0000000004544f00 ""
(v8::Local<v8::ArrayBuffer>) buffer = (val_ = 0x00007f7be416c0a6)
(v8::Local<v8::TypedArray>) dataArray = (val_ = 0x00007ffc4097c690)
(Nan::TypedArrayContents<unsigned char>) typedArrayContents = (length_ = 46777454081, data_ = "")
(uint8_t *) dst = 0x00007ffc4097c6b0 "\x97@
(const int) argc = 0
(v8::Local<v8::Int32>) swHandle = (val_ = 0x0000000000f38174)
(v8::Local<v8::Int32>) shHandle = (val_ = 0x0000149287f00119)
(v8::Local<v8::Value> [3]) argv = {
[0] = (val_ = 0x0000000002cd5585)
[1] = (val_ = 0x0000000000000000)
[2] = (val_ = 0x00007ffc4097c720)
}
(v8::Local<v8::Function>) ctor = (val_ = 0x0000000004464b70)
(v8::Local<v8::Object>) instance = (val_ = 0x00007ffc4097c6c0)
The one that stands out to me is that size
(the same size
we're passing into the ArrayBuffer
constructor) is negative. A quick dive into Node's source wasn't enough to tell me if ArrayBuffer
would throw that specific error if given a negative size, but it'd be my first guess.
Additionally, calling getImageData(0, -10, 10, 10)
will get you a crash with a completely different error message ('FATAL ERROR: v8::ToLocalChecked Empty MaybeLocal.'), but I haven't looked into that one.
Your Environment
- canvas@2.9.1
- Node v12.22.12 on Ubuntu 20.04 (via WSL), but this seems to crash on all node versions I've tried.