1 ;;;; This software is part of the SBCL system. See the README file for
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
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 ((%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
)
22 (let* ((fdefn (sb-int:find-fdefn
'zonk
))
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
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
))
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 @@
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
124 + (setf (sap-ref-32 sap insts-offs
) #x00FD60FF
)) ; JMP [RAX-3]
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
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
))
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
157 #include
"genesis/sbcl.h"
159 +#define _GNU_SOURCE
/* for REG_RAX etc. from sys
/ucontext
*/
160 +#include
<sys
/ucontext.h
>
165 @@ -
1942,6 +1945,19 @@ low_level_unblock_me_trampoline
(int signal
, siginfo_t
*info
, void
*void_context
)
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);
174 + for(i=-10; i<10; ++i) printf(" %02x", pc[i]);
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];
182 SAVE_ERRNO
(signal,context
,void_context
);
183 (*interrupt_low_level_handlers
[signal])(signal, info, context);
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)))
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
208 (f (car (truly-the cons z))))
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.
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)