Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Node crash in getImageData #2024

Open
nvladimiroff opened this issue Apr 19, 2022 · 2 comments · May be fixed by #2145
Open

Node crash in getImageData #2024

nvladimiroff opened this issue Apr 19, 2022 · 2 comments · May be fixed by #2145
Labels

Comments

@nvladimiroff
Copy link

nvladimiroff commented Apr 19, 2022

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.
@nvladimiroff nvladimiroff changed the title Segfault in getImageData Node crash in getImageData Apr 28, 2022
@zbjornson zbjornson added the Bug label Jul 7, 2022
@schw4rzlicht
Copy link

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.

I also see this when calling createImageData(x, y) if x === 0 || y === 0.

@jonaskistler
Copy link

jonaskistler commented Oct 25, 2022

I have the error FATAL ERROR: v8::ToLocalChecked Empty MaybeLocal. when getImageData is called with negative x, y values. In my specific case, the method is called from the function genericComposeSMask when the maskOffsetX and Y are bigger than the layerOffsetX and Y. Is there a possible workaround or do we have to wait a fix ? This happens in any version of canvas and/or node
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants