Description
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
.
sassc-ruby/lib/sassc/functions_handler.rb
Lines 22 to 33 in 4fce2b6
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) {
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.
Activity
kolen commentedon Jul 1, 2024
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.