safepoint: Remove unused context argument.
[sbcl.git] / tests / fin-call.impure.lisp
blob7aa764fc0074a2d1ee16df3595088be61c2ff2fd
1 ;;;; This software is part of the SBCL system. See the README file for
2 ;;;; more information.
3 ;;;;
4 ;;;; While most of SBCL is derived from the CMU CL system, the test
5 ;;;; files (like this one) were written from scratch after the fork
6 ;;;; from CMU CL.
7 ;;;;
8 ;;;; This software is in the public domain and is provided with
9 ;;;; absolutely no warranty. See the COPYING and CREDITS files for
10 ;;;; more information.
12 (defclass foo ()
13 ((%function :initarg :function :reader foo-%function))
14 (:metaclass sb-mop:funcallable-standard-class))
16 (defmethod initialize-instance :after ((f foo) &key)
17 (sb-mop:set-funcallable-instance-function f (foo-%function f)))
19 (setf (fdefinition 'zonk) (make-instance 'foo :function (lambda (y) y)))
20 (with-test (:name :call-nonstandard-funcallable-instance)
21 #+nil
22 (let* ((fdefn (sb-int:find-fdefn 'zonk))
23 (raw-entry-point
24 (sb-sys:sap-ref-sap (sb-sys:int-sap (sb-kernel:get-lisp-obj-address fdefn))
25 sb-vm::(+ (ash fdefn-raw-addr-slot word-shift)
26 (- other-pointer-lowtag))))
27 (code-obj (sb-di::code-header-from-pc raw-entry-point)))
28 ;; gets a custom trampoline because it has no embedded trampoline
29 (assert (search "#<trampoline #<" (write-to-string code-obj))))
31 ;; And it doesn't crash when called
32 (funcall 'zonk 3))
35 This file contains a test of immobile_space_preserve_pointer(),
36 but demonstrates just about nothing, as it stands.
38 The vulnerability that it fixes is one which can't
39 be caused given how the compiler currently generates code.
40 The bug is that in the following call sequence:
41 BB: FF7508 PUSH QWORD PTR [RBP+8]
42 BE: FF60FD JMP QWORD PTR [RAX-3]
43 it is possible that the _only_ pointer to a funcallable-instance is
44 in register RAX, which gets overwritten as soon as the first instruction
45 of the FIN trampoline is executed. This is a problem only if the
46 trampoline instructions are inside the FIN. (It isn't if they're not)
48 A self-contained trampoline can be disassembled from a FIN:
49 * (sb-disassem:disassemble-memory (+ (sb-kernel:get-lisp-obj-address #'print-object)
50 (- sb-vm:fun-pointer-lowtag)
51 (* 4 sb-vm:n-word-bytes))
52 10)
54 Size: 10 bytes. Origin: #x20393E60
55 0: 488B05E9FFFFFF MOV RAX, [RIP-23] ; [#x20393E50]
56 7: FF60FD JMP QWORD PTR [RAX-3]
58 As can be seen, after the MOV instruction, RAX points to the instance's
59 function, which is usually a closure. We then want to jump to the address
60 of the closure's function, dereferenced from [RAX-3]. Doing that requires
61 the instruction decoder to fetch the JMP instruction from the FIN.
62 But what if the FIN has already been trashed? If you're lucky,
63 the bytes will still be there depending on whether GC lazily or eagerly
64 clears memory. This GC does so eagerly in immobile space.
65 And Intel/AMD say that self-modifying code "works", which is bad in this
66 case because it means that the CPU fetches the modified code.
68 To produce such a situation requires a bunch of patches/coercions to:
70 (1) perform an anonymous call to a function using no additional stack
71 slots nor registers. This can only be done by hacking up the call vop
72 or writing a new one, because the compiler always wants to preserve the
73 object being called by stashing it somewhere, then moving it from
74 there into register RAX just prior to call.
75 This could change if the compiler were smarter.
77 (2) ensure that the funcallable-instance's implementation (the FIN-FUN)
78 is not a closure that back-references the funcallable instance itself,
79 because if it is, then loading the closure (i.e. executing the first
80 instruction of the trampoline) keeps the FIN live, as the closure's
81 data block points to the FIN. Requiring that you not close over the FIN
82 renders the whole concept of mutable functions slightly useless,
83 so from a practical perspective, this situation might never arise.
85 (3) ensure that there is no symbol that names the funcallable-instance.
86 In particular, it must not be a global function attached to an fdefn.
87 In theory this could happen - anonymous functions are things.
89 (4) ensure that that no method is associated with the GF specialized on
90 any class named by a symbol. (Because various global tables map names
91 of specializers to lists of methods specialized to that specializer;
92 and standard methods point to their GF)
93 This is an aspect of CLOS that everything points to everything.
94 But it's conceivable that you call a GF that has no methods.
96 (5) insert a breakpoint or debugger trap or something into the
97 funcallable-instance trampoline so that GC can occur in between
98 the first and second intructions in the trampoline.
99 This is for testing - otherwise it would be hard to trigger.
101 If you manage to do all the above, and GC occurs in between the two
102 instructions of the trampoline, then without this patch, a crash happens
103 on return from the garbage collector. Here are some diffs that will do that:
105 diff --git a/src/code/x86-64-vm.lisp b/src/code/x86-64-vm.lisp
106 index a7a8c5144..0dfa632f3 100644
107 --- a/src/code/x86-64-vm.lisp
108 +++ b/src/code/x86-64-vm.lisp
109 @@ -217,11 +217,16 @@
111 (closurep fun))))
113 +(defvar *trap-on-fin-entry* nil)
114 (defun %set-fin-trampoline (fin)
115 (let ((sap (int-sap (- (get-lisp-obj-address fin) fun-pointer-lowtag)))
116 (insts-offs (ash (1+ funcallable-instance-info-offset) word-shift)))
117 - (setf (sap-ref-word sap insts-offs) #xFFFFFFE9058B48 ; MOV RAX,[RIP-23]
118 - (sap-ref-32 sap (+ insts-offs 7)) #x00FD60FF)) ; JMP [RAX-3]
119 + (setf (sap-ref-word sap insts-offs) #xFFFFFFE9058B48) ; MOV RAX,[RIP-23]
120 + (incf insts-offs 7)
121 + (when *trap-on-fin-entry*
122 + (setf (sap-ref-8 sap insts-offs) #xCE) ; INTO - illegal instruction
123 + (incf insts-offs))
124 + (setf (sap-ref-32 sap insts-offs) #x00FD60FF)) ; JMP [RAX-3]
125 fin)
127 (defun %set-fdefn-fun (fdefn fun)
128 diff --git a/src/compiler/x86-64/call.lisp b/src/compiler/x86-64/call.lisp
129 index d66b5c90c..d33c26556 100644
130 --- a/src/compiler/x86-64/call.lisp
131 +++ b/src/compiler/x86-64/call.lisp
132 @@ -668,6 +668,7 @@
133 ;;; In tail call with fixed arguments, the passing locations are
134 ;;; passed as a more arg, but there is no new-FP, since the arguments
135 ;;; have been set up in the current frame.
136 +(defvar *clobber-rsi* nil)
137 (macrolet ((define-full-call (vop-name named return variable)
138 (aver (not (and variable (eq return :tail))))
139 #+immobile-code (when named (setq named :direct))
140 @@ -785,6 +786,10 @@
141 '((if (zerop nargs)
142 (zeroize rcx)
143 (inst mov rcx (fixnumize nargs)))))
145 + (when *clobber-rsi* ; 3rd argument-passing register
146 + (inst mov rsi-tn (fixnumize -1)))
148 ,@(cond ((eq return :tail)
149 '(;; Python has figured out what frame we should
150 ;; return to so might as well use that clue.
151 diff --git a/src/runtime/interrupt.c b/src/runtime/interrupt.c
152 index 400772889..229eea81c 100644
153 --- a/src/runtime/interrupt.c
154 +++ b/src/runtime/interrupt.c
155 @@ -42,6 +42,9 @@
157 #include "genesis/sbcl.h"
159 +#define _GNU_SOURCE /* for REG_RAX etc. from sys/ucontext */
160 +#include <sys/ucontext.h>
162 #include <stdio.h>
163 #include <stdlib.h>
164 #include <string.h>
165 @@ -1942,6 +1945,19 @@ low_level_unblock_me_trampoline(int signal, siginfo_t *info, void *void_context)
166 static void
167 low_level_handle_now_handler(int signal, siginfo_t *info, void *void_context)
169 + unsigned char *pc = (unsigned char*)
170 + ((struct ucontext*)void_context)->uc_mcontext.gregs[REG_RIP];
171 + if (*pc == 0xCE) { // this is the illegal instruction placed into a FIN
172 + printf("bytes around PC (%p):", pc);
173 + int i;
174 + for(i=-10; i<10; ++i) printf(" %02x", pc[i]);
175 + putchar('\n');
176 + printf("calling GC\n");
177 + collect_garbage(0);
178 + printf("back from GC\n");
179 + ++((struct ucontext*)void_context)->uc_mcontext.gregs[REG_RIP];
180 + return;
182 SAVE_ERRNO(signal,context,void_context);
183 (*interrupt_low_level_handlers[signal])(signal, info, context);
184 RESTORE_ERRNO;
187 (progv (let ((s1 (find-symbol "*TRAP-ON-FIN-ENTRY*" "SB-VM"))
188 (s2 (find-symbol "*CLOBBER-RSI*" "SB-VM")))
189 (if s1 (list s1 s2)))
190 '(t t)
191 (defgeneric blub (x))
192 ;; BAR calls F with only two args, so we can freely clobber RSI
193 ;; (the third call TN on x86-64), and not have RSI be an accidental
194 ;; copy of F which gets moved into RAX and is therefore not needed.
195 (setf (symbol-function 'bar)
196 (compile nil '(lambda (f) (funcall (truly-the function f) 1 2)))))
198 ;;; Just set any function that doesn't close over BLUB
199 (setf (sb-kernel:%funcallable-instance-fun #'blub)
200 (compile nil '(lambda (&rest args)
201 (format t "Can't call me ~S" args))))
203 (defglobal *z* (list #'blub)) ; capture BLUB somewhere that is not an FDEFN
204 (fmakunbound 'blub)
206 (defun foo ()
207 (let* ((z *z*)
208 (f (car (truly-the cons z))))
209 (rplaca z nil)
210 (bar f)))
211 ;;; FOO, if interpreted, holds on to *Z*'s CAR on the stack,
212 ;;; making this test not demonstrate what it should about stack pins.
213 (compile'foo)
215 ;;; The "expected" behavior of this test without the patch
216 ;;; to enliven FINs based on unboxed pointers is:
217 ;;; ::: UNEXPECTED-FAILURE :GARBAGE-FUNCALLABLE-INSTANCE-CALL-CRASH
218 ;;; due to SB-SYS:MEMORY-FAULT-ERROR: "Unhandled memory fault at #x1E31B7B."
220 (with-test (:name :garbage-funcallable-instance-call-crash)
221 (foo))