1 // A WORD OF CAUTION
2 //
3 // This entire file basically needs to be kept in sync with itself. It's not
4 // really possible to modify just one bit of this file without understanding
5 // all the other bits. Documentation tries to reference various bits here and
6 // there but try to make sure to read over everything before tweaking things!
7 //
8 // Also at this time this file is heavily based off the x86_64 file, so you'll
9 // probably want to read that one as well.
10 //
11 // Finally, control flow integrity hardening has been applied to the code using
12 // the Pointer Authentication (PAuth) and Branch Target Identification (BTI)
13 // technologies from the Arm instruction set architecture:
14 // * All callable functions start with either the `BTI c` or `PACIASP`/`PACIBSP`
15 // instructions
16 // * Return addresses are signed and authenticated using the stack pointer
17 // value as a modifier (similarly to the salt in a HMAC operation); the
18 // `DW_CFA_AARCH64_negate_ra_state` DWARF operation (aliased with the
19 // `.cfi_window_save` assembler directive) informs an unwinder about this
20
21 use core::arch::naked_asm;
22
23 cfg_if::cfg_if! {
24 if #[cfg(target_vendor = "apple")] {
25 macro_rules! paci1716 { () => ("pacib1716\n"); }
26 macro_rules! pacisp { () => ("pacibsp\n"); }
27 macro_rules! autisp { () => ("autibsp\n"); }
28 } else {
29 macro_rules! paci1716 { () => ("pacia1716\n"); }
30 macro_rules! pacisp { () => ("paciasp\n"); }
31 macro_rules! autisp { () => ("autiasp\n"); }
32 }
33 }
34
35 #[inline(never)] // FIXME(rust-lang/rust#148307)
wasmtime_fiber_switch(top_of_stack: *mut u8)36 pub(crate) unsafe extern "C" fn wasmtime_fiber_switch(top_of_stack: *mut u8) {
37 unsafe { wasmtime_fiber_switch_(top_of_stack) }
38 }
39
40 #[unsafe(naked)]
wasmtime_fiber_switch_(top_of_stack: *mut u8 )41 unsafe extern "C" fn wasmtime_fiber_switch_(top_of_stack: *mut u8 /* x0 */) {
42 naked_asm!(concat!(
43 "
44 .cfi_startproc
45 ",
46 pacisp!(),
47 "
48 .cfi_window_save
49 // Save all callee-saved registers on the stack since we're
50 // assuming they're clobbered as a result of the stack switch.
51 stp x29, x30, [sp, -16]!
52 stp x27, x28, [sp, -16]!
53 stp x25, x26, [sp, -16]!
54 stp x23, x24, [sp, -16]!
55 stp x21, x22, [sp, -16]!
56 stp x19, x20, [sp, -16]!
57 stp d14, d15, [sp, -16]!
58 stp d12, d13, [sp, -16]!
59 stp d10, d11, [sp, -16]!
60 stp d8, d9, [sp, -16]!
61
62 // Load our previously saved stack pointer to resume to, and save
63 // off our current stack pointer on where to come back to
64 // eventually.
65 ldr x8, [x0, -0x10]
66 mov x9, sp
67 str x9, [x0, -0x10]
68
69 // Switch to the new stack and restore all our callee-saved
70 // registers after the switch and return to our new stack.
71 mov sp, x8
72 ldp d8, d9, [sp], 16
73 ldp d10, d11, [sp], 16
74 ldp d12, d13, [sp], 16
75 ldp d14, d15, [sp], 16
76
77 ldp x19, x20, [sp], 16
78 ldp x21, x22, [sp], 16
79 ldp x23, x24, [sp], 16
80 ldp x25, x26, [sp], 16
81 ldp x27, x28, [sp], 16
82 ldp x29, x30, [sp], 16
83 ",
84 autisp!(),
85 "
86 .cfi_window_save
87 ret
88 .cfi_endproc
89 ",
90 ));
91 }
92
wasmtime_fiber_init( top_of_stack: *mut u8, entry_point: extern "C" fn(*mut u8, *mut u8) -> *mut u8, entry_arg0: *mut u8, )93 pub(crate) unsafe fn wasmtime_fiber_init(
94 top_of_stack: *mut u8,
95 entry_point: extern "C" fn(*mut u8, *mut u8) -> *mut u8,
96 entry_arg0: *mut u8, // x2
97 ) {
98 #[repr(C)]
99 #[derive(Default)]
100 struct InitialStack {
101 d8: u64,
102 d9: u64,
103 d10: u64,
104 d11: u64,
105 d12: u64,
106 d13: u64,
107 d14: u64,
108 d15: u64,
109
110 x19: *mut u8,
111 x20: *mut u8,
112 x21: *mut u8,
113 x22: *mut u8,
114 x23: *mut u8,
115 x24: *mut u8,
116 x25: *mut u8,
117 x26: *mut u8,
118 x27: *mut u8,
119 x28: *mut u8,
120
121 fp: *mut u8,
122 lr: *mut u8,
123
124 // unix.rs reserved space
125 last_sp: *mut u8,
126 run_result: *mut u8,
127 }
128
129 unsafe {
130 let initial_stack = top_of_stack.cast::<InitialStack>().sub(1);
131 initial_stack.write(InitialStack {
132 x19: top_of_stack,
133 x20: entry_point as *mut u8,
134 x21: entry_arg0,
135 x22: wasmtime_fiber_switch_ as *mut u8,
136
137 // We set up the newly initialized fiber, so that it resumes
138 // execution from wasmtime_fiber_start(). As a result, we need a
139 // signed address of this function because `wasmtime_fiber_switch`
140 // ends with a `auti{a,b}sp` instruction. There are 2 requirements:
141 // * We would like to use an instruction that is executed as a no-op
142 // by processors that do not support PAuth, so that the code is
143 // backward-compatible and there is no duplication; `PACIA1716` is
144 // a suitable one.
145 // * The fiber stack pointer value that is used by the signing
146 // operation must match the value when the pointer is
147 // authenticated inside wasmtime_fiber_switch(), which is 16 bytes
148 // below the `top_of_stack` which will be `sp` at the time of the
149 // `auti{a,b}sp`.
150 //
151 // TODO: Use the PACGA instruction to authenticate the saved register
152 // state, which avoids creating signed pointers to
153 // wasmtime_fiber_start(), and provides wider coverage.
154 lr: paci1716(wasmtime_fiber_start as *mut u8, top_of_stack.sub(16)),
155
156 last_sp: initial_stack.cast(),
157 ..InitialStack::default()
158 });
159 }
160 }
161
162 /// Signs `r17` with the value in `r16` using either `paci{a,b}1716` depending
163 /// on the platform.
paci1716(mut r17: *mut u8, r16: *mut u8) -> *mut u8164 fn paci1716(mut r17: *mut u8, r16: *mut u8) -> *mut u8 {
165 unsafe {
166 core::arch::asm!(
167 paci1716!(),
168 inout("x17") r17,
169 in("x16") r16,
170 );
171 r17
172 }
173 }
174
175 // See the x86_64 file for more commentary on what these CFI directives are
176 // doing. Like over there note that the relative offsets to registers here
177 // match the frame layout in `wasmtime_fiber_switch`.
178 #[unsafe(naked)]
wasmtime_fiber_start() -> !179 unsafe extern "C" fn wasmtime_fiber_start() -> ! {
180 naked_asm!(
181 "
182 .cfi_startproc simple
183 .cfi_def_cfa_offset 0
184 .cfi_escape 0x0f, /* DW_CFA_def_cfa_expression */ \
185 5, /* the byte length of this expression */ \
186 0x6f, /* DW_OP_reg31(%sp) */ \
187 0x06, /* DW_OP_deref */ \
188 0x23, 0xa0, 0x1 /* DW_OP_plus_uconst 0xa0 */
189 .cfi_rel_offset x30, -0x08
190 .cfi_rel_offset x29, -0x10
191 .cfi_window_save
192 .cfi_rel_offset x28, -0x18
193 .cfi_rel_offset x27, -0x20
194 .cfi_rel_offset x26, -0x28
195 .cfi_rel_offset x25, -0x30
196 .cfi_rel_offset x24, -0x38
197 .cfi_rel_offset x23, -0x40
198 .cfi_rel_offset x22, -0x48
199 .cfi_rel_offset x21, -0x50
200 .cfi_rel_offset x20, -0x58
201 .cfi_rel_offset x19, -0x60
202
203 // Load our two arguments from the stack, where x1 is our start
204 // procedure and x0 is its first argument. This also blows away the
205 // stack space used by those two arguments.
206 mov x0, x21
207 mov x1, x19
208
209 // ... and then we call the function! Note that this is a function call
210 // so our frame stays on the stack to backtrace through.
211 blr x20
212
213 // The entry function returns where to switch to as the final switch, so
214 // that's performed here in inline assembly.
215 blr x22
216
217 // Unreachable, here for safety. This should help catch unexpected
218 // behaviors. Use a noticeable payload so one can grep for it in the
219 // codebase.
220 brk 0xf1b3
221 .cfi_endproc
222 ",
223 );
224 }
225