-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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.