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