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

Segmentation fault when loading an SVG buffer on v3. #2486

Open
itzTheMeow opened this issue Feb 6, 2025 · 9 comments
Open

Segmentation fault when loading an SVG buffer on v3. #2486

itzTheMeow opened this issue Feb 6, 2025 · 9 comments

Comments

@itzTheMeow
Copy link

When loading an SVG from a buffer on v3.1.0, i get a segmentation fault. This works properly on v2.x. I am running this on Ubuntu 22.04 with Node 20 managed by NVM.

Reproduction steps:

// test.js
const { loadImage } = require("canvas");

loadImage(Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg"><path d="M1,1"/></svg>`));
$ node test.js
Segmentation fault (core dumped)

Backtrace:

$ gdb node

(gdb) run test.js
Starting program: /home/meow/.nvm/versions/node/v20.9.0/bin/node test.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff7600640 (LWP 966381)]
[New Thread 0x7ffff6c00640 (LWP 966383)]
[New Thread 0x7ffff6200640 (LWP 966384)]
[New Thread 0x7ffff5800640 (LWP 966385)]
[New Thread 0x7ffff4e00640 (LWP 966386)]
[New Thread 0x7ffff7e66640 (LWP 966388)]

Thread 1 "node" received signal SIGSEGV, Segmentation fault.
0x00007ffff6dd685d in g_type_check_instance_is_fundamentally_a () from /home/meow/Documents/test/node_modules/.pnpm/canvas@3.1.0/node_modules/canvas/build/Release/libgobject-2.0.so.0
(gdb) bt
#0  0x00007ffff6dd685d in g_type_check_instance_is_fundamentally_a ()
   from /home/meow/Documents/test/node_modules/.pnpm/canvas@3.1.0/node_modules/canvas/build/Release/libgobject-2.0.so.0
#1  0x00007ffff6db52d5 in g_object_unref ()
   from /home/meow/Documents/test/node_modules/.pnpm/canvas@3.1.0/node_modules/canvas/build/Release/libgobject-2.0.so.0
#2  0x00007ffff7b4be5f in Image::loadSVGFromBuffer(unsigned char*, unsigned int) ()
   from /home/meow/Documents/test/node_modules/.pnpm/canvas@3.1.0/node_modules/canvas/build/Release/canvas.node
#3  0x00007ffff7b4d332 in Image::loadFromBuffer(unsigned char*, unsigned int) ()
   from /home/meow/Documents/test/node_modules/.pnpm/canvas@3.1.0/node_modules/canvas/build/Release/canvas.node
#4  0x00007ffff7b4db80 in Image::SetSource(Napi::CallbackInfo const&) ()
   from /home/meow/Documents/test/node_modules/.pnpm/canvas@3.1.0/node_modules/canvas/build/Release/canvas.node
#5  0x00007ffff7b4e748 in Napi::details::CallbackData<void (*)(Napi::CallbackInfo const&), void>::Wrapper(napi_env__*, napi_callback_info__*) ()
   from /home/meow/Documents/test/node_modules/.pnpm/canvas@3.1.0/node_modules/canvas/build/Release/canvas.node
#6  0x0000000000c37df9 in v8impl::(anonymous namespace)::FunctionCallbackWrapper::Invoke(v8::FunctionCallbackInfo<v8::Value> const&) ()
#7  0x0000000000f26ecf in v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) ()
#8  0x0000000000f2773d in 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::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, unsigned long*, int) ()
#9  0x0000000000f27c05 in v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) ()
#10 0x0000000001931df6 in Builtins_CEntry_Return1_ArgvOnStack_BuiltinExit ()
#11 0x00000000018a3d1c in Builtins_InterpreterEntryTrampoline ()
#12 0x00000273029c04e9 in ?? ()
#13 0x0000389ecc9e2911 in ?? ()
#14 0x0000000600000000 in ?? ()
#15 0x00000273029c05b9 in ?? ()
#16 0x00002d6278e8cf69 in ?? ()
#17 0x00002d6278e8cd39 in ?? ()
#18 0x0000389ecc9e2911 in ?? ()
#19 0x000029a60631f5a9 in ?? ()
#20 0x0000003f00000000 in ?? ()
#21 0x000006684f2dd069 in ?? ()
#22 0x0000000000000003 in ?? ()
#23 0x0000377cdb30d999 in ?? ()
#24 0x0000377cdb30d919 in ?? ()
@chearon
Copy link
Collaborator

chearon commented Feb 7, 2025

Thanks, this probably happened in the NAPI change then. I'll fix it this weekend and add some tests for SVG.

@itzTheMeow
Copy link
Author

Any update on this?

@chearon
Copy link
Collaborator

chearon commented Feb 23, 2025

Are you sure that it ever worked? I dug into this (after being delayed for a while because I didn't know 2.11.2 is totally broken with modern node versions) and here's what I found: you need to specify width and height. This does work:

// test.js
const { loadImage } = require("canvas");

loadImage(Buffer.from(`<svg width="1" height="1" xmlns="http://www.w3.org/2000/svg"><path d="M1,1"/></svg>`));

Of course, it shouldn't be crashing. There is a problem in the error path where g_object_unref is called twice. I don't know why you see a crash in 3.1.0 but not in 2.11.2 because I got the opposite! It's probably undefined behavior that gets us different results because of different versions of glib.

Even without the crash, the error is totally unhelpful, so I'll add some friendlier messaging.

chearon added a commit that referenced this issue Feb 23, 2025
@itzTheMeow
Copy link
Author

My SVGs specify a viewBox, so they work properly. I thought I ran my test case on both versions- maybe I didn't.

// test.js
const { loadImage, createCanvas } = require("canvas");
const { writeFileSync } = require("fs");

loadImage(
  Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
  <rect fill="red" width="1" height="1" />
</svg>`),
).then((img) => {
  const canvas = createCanvas(10, 10),
    ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0);

  writeFileSync("image.png", canvas.toBuffer());
});

This works properly on 2.11.2

What node version are you considering modern? Works fine on v20.18.2

Thanks for the fix!

@itzTheMeow
Copy link
Author

Just tested this commit and still having issues.
My previous test case works on 2.11.2, but not on 3.

Run using latest commit:
Image
Removing the width/height and keeping the viewBox results in a segfault.

Run using v2.11.2:
Image
While removing the width/height produces the correct image still:
Image

Logging the width/height of the image is correct.
Image

$ node test.js
10 10

Another example with a more complicated SVG:

// test.js
const { loadImage, createCanvas } = require("canvas");
const { writeFileSync } = require("fs");

loadImage(
  Buffer.from(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="709 0 64 64" fill="#00c805">
  <path
    d="M737.61 50.7l-.427.14c-2.74.909-6.796 2.311-10.435 3.982-.193.093-.32.35-.32.35-.07.157-.153.349-.246.564l-.012.03c-.408.925-.968 2.316-1.213 2.887l-.187.448a.18.18 0 00.047.204.167.167 0 00.123.052.234.234 0 00.081-.017l.438-.21c.997-.471 2.257-1.188 3.575-1.816l.047-.023a2417.36 2417.36 0 007.058-3.348s.274-.145.414-.419l1.277-2.561a.187.187 0 00-.22-.263zm-10.22-3.964l1.195-2.3.035-.063a158.215 158.215 0 0120.107-29.3l.216-.25a.257.257 0 00.03-.28.252.252 0 00-.252-.128l-.327.047a161.577 161.577 0 00-15.38 2.893c-.509.14-.836.471-.906.547a172.233 172.233 0 00-10.738 14.274 1.26 1.26 0 00-.18.845c.034.262.833 6.403 2.046 11.119-3.01 8.645-5.699 20.037-5.699 20.037a.256.256 0 00.035.216.239.239 0 00.2.099h1.714c.11 0 .204-.065.245-.163l.116-.32a159.515 159.515 0 015.95-14.054c.507-1.065 1.592-3.219 1.592-3.219z" />
  <path
    d="M751.241 16.942l-.006-.326a.254.254 0 00-.164-.227.257.257 0 00-.274.07l-.216.244a155.428 155.428 0 00-22.69 34.58l-.14.291a.247.247 0 00.04.28.253.253 0 00.275.058l.297-.123a149.858 149.858 0 0115.487-5.431c.31-.087.572-.309.718-.594 2.27-4.413 7.536-12.959 7.536-12.959.134-.192.1-.477.1-.477s-.905-10.217-.963-15.386z" />
  <path
    d="M762.983 2.201c-1.29-1.117-3.162-1.641-6.067-1.705-2.636-.058-5.769.512-9.32 1.676a3.62 3.62 0 00-1.337.85A172.051 172.051 0 00735.82 13.7l-.258.28a.247.247 0 00-.029.285c.053.093.164.14.268.116l.374-.081a160.34 160.34 0 0116.053-2.591 1.233 1.233 0 011.37 1.252 147.38 147.38 0 00.572 15.677l.03.338a.249.249 0 00.186.215c.017.006.035.006.058.012a.266.266 0 00.21-.105l.192-.274a148.818 148.818 0 019.654-12.295c.385-.436.485-.71.554-1.106 1.074-6.858-.583-11.934-2.07-13.22z" />
</svg>`),
).then((img) => {
  const canvas = createCanvas(img.width, img.height),
    ctx = canvas.getContext("2d");

  ctx.drawImage(img, 0, 0);

  writeFileSync("image.png", canvas.toBuffer());
  console.log(img.width, img.height);
});
$ node test.js
64 64

Resulting image:
Image

My guess is that it uses the viewBox to determine the width/height of the SVG. In this case being 64x64.

This example also segfaults on v3, backtrace if needed:

gdb node
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from node...
(gdb) run test.js
Starting program: /home/meow/.nvm/versions/node/v20.18.2/bin/node test.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff7600640 (LWP 43888)]
[New Thread 0x7ffff6c00640 (LWP 43889)]
[New Thread 0x7ffff6200640 (LWP 43890)]
[New Thread 0x7ffff5800640 (LWP 43891)]
[New Thread 0x7ffff4e00640 (LWP 43892)]
[New Thread 0x7ffff7fba640 (LWP 43893)]

Thread 1 "node" received signal SIGSEGV, Segmentation fault.
0x00007ffff6dd685d in g_type_check_instance_is_fundamentally_a () from /home/meow/Documents/testcase/node_modules/.pnpm/canvas@https+++codeload.github.com+Automattic+node-canvas+tar.gz+9d5f104cb52644f878eb3b_4ef2929f0daa660aa2ffa12f649e4566/node_modules/canvas/build/Release/libgobject-2.0.so.0
(gdb) bt
#0  0x00007ffff6dd685d in g_type_check_instance_is_fundamentally_a ()
   from /home/meow/Documents/testcase/node_modules/.pnpm/canvas@https+++codeload.github.com+Automattic+node-canvas+tar.gz+9d5f104cb52644f878eb3b_4ef2929f0daa660aa2ffa12f649e4566/node_modules/canvas/build/Release/libgobject-2.0.so.0
#1  0x00007ffff6db52d5 in g_object_unref ()
   from /home/meow/Documents/testcase/node_modules/.pnpm/canvas@https+++codeload.github.com+Automattic+node-canvas+tar.gz+9d5f104cb52644f878eb3b_4ef2929f0daa660aa2ffa12f649e4566/node_modules/canvas/build/Release/libgobject-2.0.so.0
#2  0x00007ffff7b4be5f in Image::loadSVGFromBuffer(unsigned char*, unsigned int) ()
   from /home/meow/Documents/testcase/node_modules/.pnpm/canvas@https+++codeload.github.com+Automattic+node-canvas+tar.gz+9d5f104cb52644f878eb3b_4ef2929f0daa660aa2ffa12f649e4566/node_modules/canvas/build/Release/canvas.node
#3  0x00007ffff7b4d332 in Image::loadFromBuffer(unsigned char*, unsigned int) ()
   from /home/meow/Documents/testcase/node_modules/.pnpm/canvas@https+++codeload.github.com+Automattic+node-canvas+tar.gz+9d5f104cb52644f878eb3b_4ef2929f0daa660aa2ffa12f649e4566/node_modules/canvas/build/Release/canvas.node
#4  0x00007ffff7b4db80 in Image::SetSource(Napi::CallbackInfo const&) ()
   from /home/meow/Documents/testcase/node_modules/.pnpm/canvas@https+++codeload.github.com+Automattic+node-canvas+tar.gz+9d5f104cb52644f878eb3b_4ef2929f0daa660aa2ffa12f649e4566/node_modules/canvas/build/Release/canvas.node
#5  0x00007ffff7b4e748 in Napi::details::CallbackData<void (*)(Napi::CallbackInfo const&), void>::Wrapper(napi_env__*, napi_callback_info__*) ()
   from /home/meow/Documents/testcase/node_modules/.pnpm/canvas@https+++codeload.github.com+Automattic+node-canvas+tar.gz+9d5f104cb52644f878eb3b_4ef2929f0daa660aa2ffa12f649e4566/node_modules/canvas/build/Release/canvas.node
#6  0x0000000000c5c2c5 in v8impl::(anonymous namespace)::FunctionCallbackWrapper::Invoke(v8::FunctionCallbackInfo<v8::Value> const&) ()
#7  0x0000000000f6e70f in v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) ()
#8  0x0000000000f6ef7d in 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::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, unsigned long*, int) ()
#9  0x0000000000f6f445 in v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) ()
#10 0x0000000001979df6 in Builtins_CEntry_Return1_ArgvOnStack_BuiltinExit ()
#11 0x00000000018ebd1c in Builtins_InterpreterEntryTrampoline ()
#12 0x00000c4a6c9404e9 in ?? ()

@itzTheMeow
Copy link
Author

@chearon Should I open a new issue regarding this? I'm unable to re-open this one.

@chearon
Copy link
Collaborator

chearon commented Mar 3, 2025

Sorry to hear that. Reopening until I can take a closer look.

@chearon chearon reopened this Mar 3, 2025
@chearon
Copy link
Collaborator

chearon commented Mar 3, 2025

Librsvg says directly that viewBox is not enough, so we can't support that:

The dimensions of the following documents cannot be resolved to pixels directly, and this function would return FALSE for them:

<!-- Needs a viewport against which to compute the percentages. -->
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"/>

<!-- Does not have intrinsic width/height, just a 1:2 aspect ratio which
    needs to be fitted within a viewport. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 200"/>

I am surprised to hear about the segfaulting though.

@itzTheMeow
Copy link
Author

Odd but alright. Just the segfaulting is the issue then.

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

No branches or pull requests

2 participants