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 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
))
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
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]
99 + (when *trap-on-fin-entry
*
100 + (setf (sap-ref-8 sap insts-offs
) #xCE
) ; INTO - illegal instruction
102 + (setf (sap-ref-32 sap insts-offs
) #x00FD60FF
)) ; JMP [RAX-3]
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
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
))
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
137 +#define _GNU_SOURCE
/* for REG_RAX etc. from sys
/ucontext
*/
138 +#include
<sys
/ucontext.h
>
143 @@ -
1942,6 +1945,19 @@ low_level_unblock_me_trampoline
(int signal
, siginfo_t
*info
, void
*void_context
)
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);
152 + for(i=-10; i<10; ++i) printf(" %02x", pc[i]);
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];
160 SAVE_ERRNO
(signal,context
,void_context
);
161 (*interrupt_low_level_handlers
[signal])(signal, info, context);
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)))
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
186 (f (car (truly-the cons z))))
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.
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)