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 in sass_value_get_tag() when custom function raises non-StandardError #244

Open
kolen opened this issue Jul 1, 2024 · 1 comment · May be fixed by #245
Open

Segmentation fault in sass_value_get_tag() when custom function raises non-StandardError #244

kolen opened this issue Jul 1, 2024 · 1 comment · May be fixed by #245

Comments

@kolen
Copy link

kolen commented Jul 1, 2024

sassc-ruby supports passing custom functions to libsass as ruby functions, for example it is used in sassc-rails to define url. Generally, this mechanism is memory-safe, but if custom function raises an exception that is not a subclass of StandardError, such as SystemStackError, segmentation fault happens.

/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/engine.rb:43: [BUG] Segmentation fault at 0x0000000000000000
ruby 3.3.3 (2024-06-12 revision f1c7b6f435) +YJIT [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0179 p:---- s:1241 e:001240 CFUNC  :compile_data_context
c:0178 p:0209 s:1236 E:001140 METHOD /home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/engine.rb:43

-- C level backtrace information -------------------------------------------
/home/kolen/.asdf/installs/ruby/3.3.3/lib/libruby.so.3.3(rb_print_backtrace+0x14) [0x70428cf2b89b] /tmp/ruby-build.20240624142351.32349.tRcN10/ruby-3.3.3/vm_dump.c:820
/home/kolen/.asdf/installs/ruby/3.3.3/lib/libruby.so.3.3(rb_vm_bugreport) /tmp/ruby-build.20240624142351.32349.tRcN10/ruby-3.3.3/vm_dump.c:1151
/home/kolen/.asdf/installs/ruby/3.3.3/lib/libruby.so.3.3(rb_bug_for_fatal_signal+0x100) [0x70428cd18ae0] /tmp/ruby-build.20240624142351.32349.tRcN10/ruby-3.3.3/error.c:1065
/home/kolen/.asdf/installs/ruby/3.3.3/lib/libruby.so.3.3(sigsegv+0x4b) [0x70428ce70a3b] /tmp/ruby-build.20240624142351.32349.tRcN10/ruby-3.3.3/signal.c:926
/usr/lib/libc.so.6(0x70428c88dae0) [0x70428c88dae0]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(sass_value_get_tag+0xc) [0x70426fe9a736]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(_ZN4Sass4EvalclEPNS_13Function_CallE+0x1de0) [0x70426fd84936]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(_ZN4Sass13Function_Call7performEPNS_9OperationIPNS_10ExpressionEEE+0x30) [0x70426fd103fa]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(_ZN4Sass6ExpandclEPNS_10AssignmentE+0xa23) [0x70426fda033d]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(_ZN4Sass10Assignment7performEPNS_9OperationIPNS_9StatementEEE+0x2e) [0x70426fcb66b0]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(_ZN4Sass6Expand12append_blockEPNS_5BlockE+0xce) [0x70426fda6a9a]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(_ZN4Sass6ExpandclEPNS_5BlockE+0x15d) [0x70426fd9d1d7]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(_ZN4Sass7Context7compileEv+0x2cf) [0x70426fd44a53]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(_ZN4Sass12Data_Context5parseEv+0x47d) [0x70426fd445bb]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(0x70426fe96a3b) [0x70426fe96a3b]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(sass_compiler_parse+0xb2) [0x70426fe977af]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(0x70426fe96faa) [0x70426fe96faa]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sassc-2.4.0/lib/sassc/libsass.so(sass_compile_data_context+0xc3) [0x70426fe97503]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/ffi-1.17.0-x86_64-linux-gnu/lib/3.3/ffi_c.so(0x7042700f9052) [0x7042700f9052]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/ffi-1.17.0-x86_64-linux-gnu/lib/3.3/ffi_c.so(0x7042700f84a3) [0x7042700f84a3]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/ffi-1.17.0-x86_64-linux-gnu/lib/3.3/ffi_c.so(0x7042700f861e) [0x7042700f861e]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/ffi-1.17.0-x86_64-linux-gnu/lib/3.3/ffi_c.so(rbffi_CallFunction+0x110) [0x7042700ec210]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/ffi-1.17.0-x86_64-linux-gnu/lib/3.3/ffi_c.so(0x7042700efe2d) [0x7042700efe2d]
/home/kolen/.asdf/installs/ruby/3.3.3/lib/libruby.so.3.3(vm_cfp_consistent_p+0x0) [0x70428cefc911] /tmp/ruby-build.20240624142351.32349.tRcN10/ruby-3.3.3/vm_insnhelper.c:3490
/home/kolen/.asdf/installs/ruby/3.3.3/lib/libruby.so.3.3(vm_call_cfunc_with_frame_) /tmp/ruby-build.20240624142351.32349.tRcN10/ruby-3.3.3/vm_insnhelper.c:3492
/home/kolen/.asdf/installs/ruby/3.3.3/lib/libruby.so.3.3(vm_call_cfunc_with_frame) /tmp/ruby-build.20240624142351.32349.tRcN10/ruby-3.3.3/vm_insnhelper.c:3518

This example causes segmentation fault by causing custom function to raise SystemStackError by infinite recursive call:

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'sassc', '2.4.0'
end

require 'sassc'

stylesheet = '$demo: demo();'

module DemoFunctions
  def demo
    demo
  end
end

SassC::Engine.new(stylesheet, syntax: :scss, functions: DemoFunctions).render

Function wrapper catches only StandardError. If non-StandardError exception occurs, ffi gem ignores exception in FFI::Function's block and behaves like the function is returned nil.

Script.custom_functions(functions: functions).each_with_index do |custom_function, i|
@callbacks[custom_function] = FFI::Function.new(:pointer, [:pointer, :pointer]) do |native_argument_list, cookie|
begin
function_arguments = arguments_from_native_list(native_argument_list)
result = functions_wrapper.send(custom_function, *function_arguments)
to_native_value(result)
rescue StandardError => exception
# This rescues any exceptions that occur either in value conversion
# or during the execution of a custom function.
error(exception.message)
end
end

libsass requires return value from custom function to be a valid pointer to Sass_Value. This return value is then passed to sass_value_get_tag, which tries to dereference null pointer.
https://github.com/sass/libsass/blob/8d312a1c91bb7dd22883ebdfc829003f75a82396/src/eval.cpp#L1107-L1108

      union Sass_Value* c_val = c_func(c_args, c_function, compiler());
      if (sass_value_get_tag(c_val) == SASS_ERROR) {

https://github.com/sass/libsass/blob/8d312a1c91bb7dd22883ebdfc829003f75a82396/src/sass_values.cpp#L16-L17

  enum Sass_Tag ADDCALL sass_value_get_tag(const union Sass_Value* v) { return v->unknown.tag; }

This might be cause of #133, #197, #207 (unsure).

Maybe this can be fixed by just catching all exceptions:

--- a/lib/sassc/functions_handler.rb	2024-07-01 17:12:43.522095555 +0300
+++ b/lib/sassc/functions_handler.rb	2024-07-01 16:37:31.255145300 +0300
@@ -25,7 +25,7 @@
             function_arguments = arguments_from_native_list(native_argument_list)
             result = functions_wrapper.send(custom_function, *function_arguments)
             to_native_value(result)
-          rescue StandardError => exception
+          rescue Exception => exception
             # This rescues any exceptions that occur either in value conversion
             # or during the execution of a custom function.
             error(exception.message)

It works for my case, catching "stack level too deep" and converting to sass compilation error, but I'm not sure if it's overall a good idea to catch non-StandardError exceptions and what consequences might it cause, especially when working near ffi boundaries. Maybe calling Kernel#abort in case of non-StandardError is a better idea.

@kolen
Copy link
Author

kolen commented Jul 1, 2024

Maybe calling Kernel#abort in case of non-StandardError is a better idea.

Tried it, it does not abort ruby process immediately, it also causes ffi callback function to return with null pointer. So first, error message is printed and then segfault still happens.

I don't know a proper way to abort inside ffi callbacks, so maybe just rescuing all exceptions with the same handler that returns error value is okay.

@kolen kolen linked a pull request Jul 2, 2024 that will close this issue
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

Successfully merging a pull request may close this issue.

1 participant