Subject: Segfault when printing a call-with-values stack frame in backtrace

From: "Thompson, David" <[email-masked]>
Date: Thu, 4 May 2023 12:29:40 -0400

Hello there,

Guile seems to segfault when trying to print certain backtraces with a
'call-with-values' stack frame.  Here's a minimal reproducer program:

(symbol? (call-with-values (lambda () (error 'oh-no)) list))

If you eval this at the REPL and enter ,bt in the debugger, Guile
should segfault.

Relevant gdb backtrace:

#0  0x00007ffff7f43397 in scm_is_values (x=<error reading variable:
ERROR: Cannot access memory at address 0x0>0x0) at
/tmp/guix-build-guile-3.0.9.drv-0/guile-3.0.9/libguile/values.h:30
#1  vm_regular_engine (thread=0x7ffff7401900) at
/tmp/guix-build-guile-3.0.9.drv-0/guile-3.0.9/libguile/vm-engine.c:974
#2  0x00007ffff7f50db5 in scm_call_n (proc=<optimized out>,
argv=<optimized out>, nargs=4) at
/tmp/guix-build-guile-3.0.9.drv-0/guile-3.0.9/libguile/vm.c:1615
#3  0x00007ffff7ebb674 in scm_call_4 (proc=<optimized out>,
arg1=<optimized out>, arg2=<optimized out>, arg3=<optimized out>,
arg4=<optimized out>) at
/tmp/guix-build-guile-3.0.9.drv-0/guile-3.0.9/libguile/eval.c:517
#4  0x00007ffff7eb801b in display_backtrace_body (a=0x7fffed68e6a0) at
/tmp/guix-build-guile-3.0.9.drv-0/guile-3.0.9/libguile/backtrace.c:239
#5  0x00007ffff7f62092 in scm_c_with_exception_handler.constprop.0
(type=#t, handler_data=handler_data@entry=0x7fffed68e630,
thunk_data=thunk_data@entry=0x7fffed68e630, thunk=<optimized out>,
handler=<optimized out>)
    at /tmp/guix-build-guile-3.0.9.drv-0/guile-3.0.9/libguile/exceptions.c:170

>From what I can tell, in vm-engine.c, in the subr-call op,
'scm_apply_subr (sp, idx, FRAME_LOCALS_COUNT ())' is called and
returns an SCM object, which is stored in the local variable 'ret'.
Then 'scm_is_values (ret)' is called, and the segfault occurs due to a
null pointer dereference. At the time of the segfault, 'idx' is 1130,
corresponding to the 'frame-local-ref' subr. Makes sense since it's
the backtrace printer that crashes Guile.

It seems that the data stored in the stack frames for
'call-with-values' calls is incorrect and the backtrace printer wants
to display 3 arguments for the call when the procedure only accepts 2
arguments.  This can be clearly seen by running another small program
that happens to not segfault:

(call-with-values (lambda () (error 'oh-no)) list)

Check out the backtrace and you'll see a frame like this:

(_ #<procedure b34e28 at <unknown port>:8:18 ()> #<procedure list _> 1)

The first 2 arguments are as expected, but why is there a 1 at the end?

For another example that doesn't crash but has an even more clearly
messed up backtrace, try this:

(append '(a b c) (call-with-values (lambda () (error 'oh-no)) list))

You'll see a frame like this:

(_ #<procedure b39730 at <unknown port>:10:35 ()> #<procedure list _>
. #<unknown-type (0xb . 0x304) @ 0x7f50dd792050>)

At Spritely we've reproduced this issue on multiple machines with both
Guile 3.0.7 and 3.0.9.

- Dave