1 | // Copyright 2017 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at |
3 | // http://rust-lang.org/COPYRIGHT. |
4 | // |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
8 | // option. This file may not be copied, modified, or distributed |
9 | // except according to those terms. |
10 | |
11 | //! This module defines the `__rust_probestack` intrinsic which is used in the |
12 | //! implementation of "stack probes" on certain platforms. |
13 | //! |
14 | //! The purpose of a stack probe is to provide a static guarantee that if a |
15 | //! thread has a guard page then a stack overflow is guaranteed to hit that |
16 | //! guard page. If a function did not have a stack probe then there's a risk of |
17 | //! having a stack frame *larger* than the guard page, so a function call could |
18 | //! skip over the guard page entirely and then later hit maybe the heap or |
19 | //! another thread, possibly leading to security vulnerabilities such as [The |
20 | //! Stack Clash], for example. |
21 | //! |
22 | //! [The Stack Clash]: https://blog.qualys.com/securitylabs/2017/06/19/the-stack-clash |
23 | //! |
24 | //! The `__rust_probestack` is called in the prologue of functions whose stack |
25 | //! size is larger than the guard page, for example larger than 4096 bytes on |
26 | //! x86. This function is then responsible for "touching" all pages relevant to |
27 | //! the stack to ensure that that if any of them are the guard page we'll hit |
28 | //! them guaranteed. |
29 | //! |
30 | //! The precise ABI for how this function operates is defined by LLVM. There's |
31 | //! no real documentation as to what this is, so you'd basically need to read |
32 | //! the LLVM source code for reference. Often though the test cases can be |
33 | //! illuminating as to the ABI that's generated, or just looking at the output |
34 | //! of `llc`. |
35 | //! |
36 | //! Note that `#[naked]` is typically used here for the stack probe because the |
37 | //! ABI corresponds to no actual ABI. |
38 | //! |
39 | //! Finally it's worth noting that at the time of this writing LLVM only has |
40 | //! support for stack probes on x86 and x86_64. There's no support for stack |
41 | //! probes on any other architecture like ARM or PowerPC64. LLVM I'm sure would |
42 | //! be more than welcome to accept such a change! |
43 | |
44 | #![cfg (not(feature = "mangled-names" ))] |
45 | // Windows and Cygwin already has builtins to do this. |
46 | #![cfg (not(any(windows, target_os = "cygwin" )))] |
47 | // All these builtins require assembly |
48 | #![cfg (not(feature = "no-asm" ))] |
49 | // We only define stack probing for these architectures today. |
50 | #![cfg (any(target_arch = "x86_64" , target_arch = "x86" ))] |
51 | |
52 | // Our goal here is to touch each page between %rsp+8 and %rsp+8-%rax, |
53 | // ensuring that if any pages are unmapped we'll make a page fault. |
54 | // |
55 | // FIXME(abi_custom): This function is unsafe because it uses a custom ABI, |
56 | // it does not actually match `extern "C"`. |
57 | // |
58 | // The ABI here is that the stack frame size is located in `%rax`. Upon |
59 | // return we're not supposed to modify `%rsp` or `%rax`. |
60 | #[cfg (target_arch = "x86_64" )] |
61 | #[unsafe(naked )] |
62 | #[rustc_std_internal_symbol ] |
63 | pub unsafe extern "C" fn __rust_probestack() { |
64 | #[cfg (not(all(target_env = "sgx" , target_vendor = "fortanix" )))] |
65 | macro_rules! ret { |
66 | () => { |
67 | "ret" |
68 | }; |
69 | } |
70 | |
71 | #[cfg (all(target_env = "sgx" , target_vendor = "fortanix" ))] |
72 | macro_rules! ret { |
73 | // for this target, [manually patch for LVI]. |
74 | // |
75 | // [manually patch for LVI]: https://software.intel.com/security-software-guidance/insights/deep-dive-load-value-injection#specialinstructions |
76 | () => { |
77 | " |
78 | pop %r11 |
79 | lfence |
80 | jmp *%r11 |
81 | " |
82 | }; |
83 | } |
84 | |
85 | core::arch::naked_asm!( |
86 | " |
87 | .cfi_startproc |
88 | pushq %rbp |
89 | .cfi_adjust_cfa_offset 8 |
90 | .cfi_offset %rbp, -16 |
91 | movq %rsp, %rbp |
92 | .cfi_def_cfa_register %rbp |
93 | |
94 | mov %rax,%r11 // duplicate %rax as we're clobbering %r11 |
95 | |
96 | // Main loop, taken in one page increments. We're decrementing rsp by |
97 | // a page each time until there's less than a page remaining. We're |
98 | // guaranteed that this function isn't called unless there's more than a |
99 | // page needed. |
100 | // |
101 | // Note that we're also testing against `8(%rsp)` to account for the 8 |
102 | // bytes pushed on the stack orginally with our return address. Using |
103 | // `8(%rsp)` simulates us testing the stack pointer in the caller's |
104 | // context. |
105 | |
106 | // It's usually called when %rax >= 0x1000, but that's not always true. |
107 | // Dynamic stack allocation, which is needed to implement unsized |
108 | // rvalues, triggers stackprobe even if %rax < 0x1000. |
109 | // Thus we have to check %r11 first to avoid segfault. |
110 | cmp $0x1000,%r11 |
111 | jna 3f |
112 | 2: |
113 | sub $0x1000,%rsp |
114 | test %rsp,8(%rsp) |
115 | sub $0x1000,%r11 |
116 | cmp $0x1000,%r11 |
117 | ja 2b |
118 | |
119 | 3: |
120 | // Finish up the last remaining stack space requested, getting the last |
121 | // bits out of r11 |
122 | sub %r11,%rsp |
123 | test %rsp,8(%rsp) |
124 | |
125 | // Restore the stack pointer to what it previously was when entering |
126 | // this function. The caller will readjust the stack pointer after we |
127 | // return. |
128 | add %rax,%rsp |
129 | |
130 | leave |
131 | .cfi_def_cfa_register %rsp |
132 | .cfi_adjust_cfa_offset -8 |
133 | " , |
134 | ret!(), |
135 | " |
136 | .cfi_endproc |
137 | " , |
138 | options(att_syntax) |
139 | ) |
140 | } |
141 | |
142 | #[cfg (all(target_arch = "x86" , not(target_os = "uefi" )))] |
143 | // This is the same as x86_64 above, only translated for 32-bit sizes. Note |
144 | // that on Unix we're expected to restore everything as it was, this |
145 | // function basically can't tamper with anything. |
146 | // |
147 | // FIXME(abi_custom): This function is unsafe because it uses a custom ABI, |
148 | // it does not actually match `extern "C"`. |
149 | // |
150 | // The ABI here is the same as x86_64, except everything is 32-bits large. |
151 | #[unsafe(naked )] |
152 | #[rustc_std_internal_symbol ] |
153 | pub unsafe extern "C" fn __rust_probestack() { |
154 | core::arch::naked_asm!( |
155 | " |
156 | .cfi_startproc |
157 | push %ebp |
158 | .cfi_adjust_cfa_offset 4 |
159 | .cfi_offset %ebp, -8 |
160 | mov %esp, %ebp |
161 | .cfi_def_cfa_register %ebp |
162 | push %ecx |
163 | mov %eax,%ecx |
164 | |
165 | cmp $0x1000,%ecx |
166 | jna 3f |
167 | 2: |
168 | sub $0x1000,%esp |
169 | test %esp,8(%esp) |
170 | sub $0x1000,%ecx |
171 | cmp $0x1000,%ecx |
172 | ja 2b |
173 | |
174 | 3: |
175 | sub %ecx,%esp |
176 | test %esp,8(%esp) |
177 | |
178 | add %eax,%esp |
179 | pop %ecx |
180 | leave |
181 | .cfi_def_cfa_register %esp |
182 | .cfi_adjust_cfa_offset -4 |
183 | ret |
184 | .cfi_endproc |
185 | " , |
186 | options(att_syntax) |
187 | ) |
188 | } |
189 | |
190 | #[cfg (all(target_arch = "x86" , target_os = "uefi" ))] |
191 | // UEFI target is windows like target. LLVM will do _chkstk things like windows. |
192 | // probestack function will also do things like _chkstk in MSVC. |
193 | // So we need to sub %ax %sp in probestack when arch is x86. |
194 | // |
195 | // FIXME(abi_custom): This function is unsafe because it uses a custom ABI, |
196 | // it does not actually match `extern "C"`. |
197 | // |
198 | // REF: Rust commit(74e80468347) |
199 | // rust\src\llvm-project\llvm\lib\Target\X86\X86FrameLowering.cpp: 805 |
200 | // Comments in LLVM: |
201 | // MSVC x32's _chkstk and cygwin/mingw's _alloca adjust %esp themselves. |
202 | // MSVC x64's __chkstk and cygwin/mingw's ___chkstk_ms do not adjust %rsp |
203 | // themselves. |
204 | #[unsafe(naked )] |
205 | #[rustc_std_internal_symbol ] |
206 | pub unsafe extern "C" fn __rust_probestack() { |
207 | core::arch::naked_asm!( |
208 | " |
209 | .cfi_startproc |
210 | push %ebp |
211 | .cfi_adjust_cfa_offset 4 |
212 | .cfi_offset %ebp, -8 |
213 | mov %esp, %ebp |
214 | .cfi_def_cfa_register %ebp |
215 | push %ecx |
216 | push %edx |
217 | mov %eax,%ecx |
218 | |
219 | cmp $0x1000,%ecx |
220 | jna 3f |
221 | 2: |
222 | sub $0x1000,%esp |
223 | test %esp,8(%esp) |
224 | sub $0x1000,%ecx |
225 | cmp $0x1000,%ecx |
226 | ja 2b |
227 | |
228 | 3: |
229 | sub %ecx,%esp |
230 | test %esp,8(%esp) |
231 | mov 4(%ebp),%edx |
232 | mov %edx, 12(%esp) |
233 | add %eax,%esp |
234 | pop %edx |
235 | pop %ecx |
236 | leave |
237 | |
238 | sub %eax, %esp |
239 | .cfi_def_cfa_register %esp |
240 | .cfi_adjust_cfa_offset -4 |
241 | ret |
242 | .cfi_endproc |
243 | " , |
244 | options(att_syntax) |
245 | ) |
246 | } |
247 | |