1
1
#![ cfg_attr( test, allow( dead_code) ) ]
2
2
3
- pub use self :: imp:: { cleanup, init} ;
4
- use self :: imp:: { drop_handler, make_handler} ;
5
-
6
- pub struct Handler {
7
- data : * mut libc:: c_void ,
8
- }
9
-
10
- impl Handler {
11
- pub unsafe fn new ( ) -> Handler {
12
- make_handler ( false )
13
- }
14
-
15
- fn null ( ) -> Handler {
16
- Handler { data : crate :: ptr:: null_mut ( ) }
17
- }
18
- }
19
-
20
- impl Drop for Handler {
21
- fn drop ( & mut self ) {
22
- unsafe {
23
- drop_handler ( self . data ) ;
24
- }
25
- }
26
- }
3
+ pub use self :: imp:: { cleanup, protect} ;
27
4
28
5
#[ cfg( any(
29
6
target_os = "linux" ,
@@ -45,22 +22,23 @@ mod imp {
45
22
#[ cfg( all( target_os = "linux" , target_env = "gnu" ) ) ]
46
23
use libc:: { mmap64, mprotect, munmap} ;
47
24
48
- use super :: Handler ;
49
- use crate :: cell:: Cell ;
50
25
use crate :: ops:: Range ;
51
26
use crate :: sync:: OnceLock ;
52
- use crate :: sync:: atomic:: { AtomicBool , AtomicPtr , AtomicUsize , Ordering } ;
27
+ use crate :: sync:: atomic:: { AtomicBool , AtomicUsize , Ordering } ;
53
28
use crate :: sys:: pal:: unix:: os;
29
+ use crate :: sys:: thread_local:: { guard, local_pointer} ;
54
30
use crate :: { io, mem, ptr, thread} ;
55
31
56
32
// We use a TLS variable to store the address of the guard page. While TLS
57
33
// variables are not guaranteed to be signal-safe, this works out in practice
58
34
// since we make sure to write to the variable before the signal stack is
59
35
// installed, thereby ensuring that the variable is always allocated when
60
36
// the signal handler is called.
61
- thread_local ! {
62
- // FIXME: use `Range` once that implements `Copy`.
63
- static GUARD : Cell <( usize , usize ) > = const { Cell :: new( ( 0 , 0 ) ) } ;
37
+ local_pointer ! {
38
+ static GUARD_START ;
39
+ static GUARD_END ;
40
+
41
+ static SIGALTSTACK ;
64
42
}
65
43
66
44
// Signal handler for the SIGSEGV and SIGBUS handlers. We've got guard pages
@@ -93,7 +71,9 @@ mod imp {
93
71
info : * mut libc:: siginfo_t ,
94
72
_data : * mut libc:: c_void ,
95
73
) {
96
- let ( start, end) = GUARD . get ( ) ;
74
+ let start = GUARD_START . get ( ) . addr ( ) ;
75
+ let end = GUARD_END . get ( ) . addr ( ) ;
76
+
97
77
// SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`.
98
78
let addr = unsafe { ( * info) . si_addr ( ) . addr ( ) } ;
99
79
@@ -119,51 +99,72 @@ mod imp {
119
99
}
120
100
121
101
static PAGE_SIZE : AtomicUsize = AtomicUsize :: new ( 0 ) ;
122
- static MAIN_ALTSTACK : AtomicPtr < libc:: c_void > = AtomicPtr :: new ( ptr:: null_mut ( ) ) ;
123
102
static NEED_ALTSTACK : AtomicBool = AtomicBool :: new ( false ) ;
124
103
104
+ /// Set up stack overflow protection for the current thread
105
+ ///
125
106
/// # Safety
126
- /// Must be called only once
107
+ /// May only be called once per thread.
127
108
#[ forbid( unsafe_op_in_unsafe_fn) ]
128
- pub unsafe fn init ( ) {
129
- PAGE_SIZE . store ( os:: page_size ( ) , Ordering :: Relaxed ) ;
130
-
131
- // Always write to GUARD to ensure the TLS variable is allocated.
132
- let guard = unsafe { install_main_guard ( ) . unwrap_or ( 0 ..0 ) } ;
133
- GUARD . set ( ( guard. start , guard. end ) ) ;
134
-
135
- // SAFETY: assuming all platforms define struct sigaction as "zero-initializable"
136
- let mut action: sigaction = unsafe { mem:: zeroed ( ) } ;
137
- for & signal in & [ SIGSEGV , SIGBUS ] {
138
- // SAFETY: just fetches the current signal handler into action
139
- unsafe { sigaction ( signal, ptr:: null_mut ( ) , & mut action) } ;
140
- // Configure our signal handler if one is not already set.
141
- if action. sa_sigaction == SIG_DFL {
142
- if !NEED_ALTSTACK . load ( Ordering :: Relaxed ) {
143
- // haven't set up our sigaltstack yet
144
- NEED_ALTSTACK . store ( true , Ordering :: Release ) ;
145
- let handler = unsafe { make_handler ( true ) } ;
146
- MAIN_ALTSTACK . store ( handler. data , Ordering :: Relaxed ) ;
147
- mem:: forget ( handler) ;
109
+ pub unsafe fn protect ( main_thread : bool ) {
110
+ if main_thread {
111
+ PAGE_SIZE . store ( os:: page_size ( ) , Ordering :: Relaxed ) ;
112
+ // Use acquire ordering to observe the page size store above,
113
+ // which is propagated by a release store to NEED_ALTSTACK.
114
+ } else if !NEED_ALTSTACK . load ( Ordering :: Acquire ) {
115
+ return ;
116
+ }
117
+
118
+ let guard = if main_thread {
119
+ unsafe { install_main_guard ( ) . unwrap_or ( 0 ..0 ) }
120
+ } else {
121
+ unsafe { current_guard ( ) . unwrap_or ( 0 ..0 ) }
122
+ } ;
123
+
124
+ // Always store the guard range to ensure the TLS variables are allocated.
125
+ GUARD_START . set ( ptr:: without_provenance_mut ( guard. start ) ) ;
126
+ GUARD_END . set ( ptr:: without_provenance_mut ( guard. end ) ) ;
127
+
128
+ if main_thread {
129
+ // SAFETY: assuming all platforms define struct sigaction as "zero-initializable"
130
+ let mut action: sigaction = unsafe { mem:: zeroed ( ) } ;
131
+ for & signal in & [ SIGSEGV , SIGBUS ] {
132
+ // SAFETY: just fetches the current signal handler into action
133
+ unsafe { sigaction ( signal, ptr:: null_mut ( ) , & mut action) } ;
134
+ // Configure our signal handler if one is not already set.
135
+ if action. sa_sigaction == SIG_DFL {
136
+ if !NEED_ALTSTACK . load ( Ordering :: Relaxed ) {
137
+ // Set up the signal stack and tell other threads to set
138
+ // up their own. This uses a release store to propagate
139
+ // the store to PAGE_SIZE above.
140
+ NEED_ALTSTACK . store ( true , Ordering :: Release ) ;
141
+ unsafe { setup_sigaltstack ( ) } ;
142
+ }
143
+
144
+ action. sa_flags = SA_SIGINFO | SA_ONSTACK ;
145
+ action. sa_sigaction = signal_handler as sighandler_t ;
146
+ // SAFETY: only overriding signals if the default is set
147
+ unsafe { sigaction ( signal, & action, ptr:: null_mut ( ) ) } ;
148
148
}
149
- action. sa_flags = SA_SIGINFO | SA_ONSTACK ;
150
- action. sa_sigaction = signal_handler as sighandler_t ;
151
- // SAFETY: only overriding signals if the default is set
152
- unsafe { sigaction ( signal, & action, ptr:: null_mut ( ) ) } ;
153
149
}
150
+ } else {
151
+ unsafe { setup_sigaltstack ( ) } ;
154
152
}
155
153
}
156
154
157
155
/// # Safety
158
- /// Must be called only once
156
+ /// Mutates the alternate signal stack
159
157
#[ forbid( unsafe_op_in_unsafe_fn) ]
160
- pub unsafe fn cleanup ( ) {
161
- // FIXME: I probably cause more bugs than I'm worth!
162
- // see https://github.com/rust-lang/rust/issues/111272
163
- unsafe { drop_handler ( MAIN_ALTSTACK . load ( Ordering :: Relaxed ) ) } ;
164
- }
158
+ unsafe fn setup_sigaltstack ( ) {
159
+ // SAFETY: assuming stack_t is zero-initializable
160
+ let mut stack = unsafe { mem:: zeroed ( ) } ;
161
+ // SAFETY: reads current stack_t into stack
162
+ unsafe { sigaltstack ( ptr:: null ( ) , & mut stack) } ;
163
+ // Do not overwrite the stack if one is already set.
164
+ if stack. ss_flags & SS_DISABLE == 0 {
165
+ return ;
166
+ }
165
167
166
- unsafe fn get_stack ( ) -> libc:: stack_t {
167
168
// OpenBSD requires this flag for stack mapping
168
169
// otherwise the said mapping will fail as a no-op on most systems
169
170
// and has a different meaning on FreeBSD
@@ -185,82 +186,60 @@ mod imp {
185
186
let sigstack_size = sigstack_size ( ) ;
186
187
let page_size = PAGE_SIZE . load ( Ordering :: Relaxed ) ;
187
188
188
- let stackp = mmap64 (
189
- ptr:: null_mut ( ) ,
190
- sigstack_size + page_size,
191
- PROT_READ | PROT_WRITE ,
192
- flags,
193
- -1 ,
194
- 0 ,
195
- ) ;
196
- if stackp == MAP_FAILED {
189
+ let allocation = unsafe {
190
+ mmap64 ( ptr:: null_mut ( ) , sigstack_size + page_size, PROT_READ | PROT_WRITE , flags, -1 , 0 )
191
+ } ;
192
+ if allocation == MAP_FAILED {
197
193
panic ! ( "failed to allocate an alternative stack: {}" , io:: Error :: last_os_error( ) ) ;
198
194
}
199
- let guard_result = libc:: mprotect ( stackp, page_size, PROT_NONE ) ;
195
+
196
+ let guard_result = unsafe { libc:: mprotect ( allocation, page_size, PROT_NONE ) } ;
200
197
if guard_result != 0 {
201
198
panic ! ( "failed to set up alternative stack guard page: {}" , io:: Error :: last_os_error( ) ) ;
202
199
}
203
- let stackp = stackp. add ( page_size) ;
204
200
205
- libc:: stack_t { ss_sp : stackp, ss_flags : 0 , ss_size : sigstack_size }
201
+ let stack = libc:: stack_t {
202
+ // Reserve a guard page at the bottom of the allocation.
203
+ ss_sp : unsafe { allocation. add ( page_size) } ,
204
+ ss_flags : 0 ,
205
+ ss_size : sigstack_size,
206
+ } ;
207
+ // SAFETY: We warned our caller this would happen!
208
+ unsafe {
209
+ sigaltstack ( & stack, ptr:: null_mut ( ) ) ;
210
+ }
211
+
212
+ // Ensure that `rt::thread_cleanup` gets called, which will in turn call
213
+ // cleanup, where this signal stack will be freed.
214
+ guard:: enable ( ) ;
215
+ SIGALTSTACK . set ( allocation. cast ( ) ) ;
206
216
}
207
217
208
- /// # Safety
209
- /// Mutates the alternate signal stack
210
- #[ forbid( unsafe_op_in_unsafe_fn) ]
211
- pub unsafe fn make_handler ( main_thread : bool ) -> Handler {
212
- if !NEED_ALTSTACK . load ( Ordering :: Acquire ) {
213
- return Handler :: null ( ) ;
218
+ pub fn cleanup ( ) {
219
+ let allocation = SIGALTSTACK . get ( ) ;
220
+ if allocation. is_null ( ) {
221
+ return ;
214
222
}
215
223
216
- if !main_thread {
217
- // Always write to GUARD to ensure the TLS variable is allocated.
218
- let guard = unsafe { current_guard ( ) } . unwrap_or ( 0 ..0 ) ;
219
- GUARD . set ( ( guard. start , guard. end ) ) ;
220
- }
224
+ SIGALTSTACK . set ( ptr:: null_mut ( ) ) ;
221
225
222
- // SAFETY: assuming stack_t is zero-initializable
223
- let mut stack = unsafe { mem:: zeroed ( ) } ;
224
- // SAFETY: reads current stack_t into stack
225
- unsafe { sigaltstack ( ptr:: null ( ) , & mut stack) } ;
226
- // Configure alternate signal stack, if one is not already set.
227
- if stack. ss_flags & SS_DISABLE != 0 {
228
- // SAFETY: We warned our caller this would happen!
229
- unsafe {
230
- stack = get_stack ( ) ;
231
- sigaltstack ( & stack, ptr:: null_mut ( ) ) ;
232
- }
233
- Handler { data : stack. ss_sp as * mut libc:: c_void }
234
- } else {
235
- Handler :: null ( )
236
- }
237
- }
226
+ let sigstack_size = sigstack_size ( ) ;
227
+ let page_size = PAGE_SIZE . load ( Ordering :: Relaxed ) ;
238
228
239
- /// # Safety
240
- /// Must be called
241
- /// - only with our handler or nullptr
242
- /// - only when done with our altstack
243
- /// This disables the alternate signal stack!
244
- #[ forbid( unsafe_op_in_unsafe_fn) ]
245
- pub unsafe fn drop_handler ( data : * mut libc:: c_void ) {
246
- if !data. is_null ( ) {
247
- let sigstack_size = sigstack_size ( ) ;
248
- let page_size = PAGE_SIZE . load ( Ordering :: Relaxed ) ;
249
- let disabling_stack = libc:: stack_t {
250
- ss_sp : ptr:: null_mut ( ) ,
251
- ss_flags : SS_DISABLE ,
252
- // Workaround for bug in macOS implementation of sigaltstack
253
- // UNIX2003 which returns ENOMEM when disabling a stack while
254
- // passing ss_size smaller than MINSIGSTKSZ. According to POSIX
255
- // both ss_sp and ss_size should be ignored in this case.
256
- ss_size : sigstack_size,
257
- } ;
258
- // SAFETY: we warned the caller this disables the alternate signal stack!
259
- unsafe { sigaltstack ( & disabling_stack, ptr:: null_mut ( ) ) } ;
260
- // SAFETY: We know from `get_stackp` that the alternate stack we installed is part of
261
- // a mapping that started one page earlier, so walk back a page and unmap from there.
262
- unsafe { munmap ( data. sub ( page_size) , sigstack_size + page_size) } ;
263
- }
229
+ let disabling_stack = libc:: stack_t {
230
+ ss_sp : ptr:: null_mut ( ) ,
231
+ ss_flags : SS_DISABLE ,
232
+ // Workaround for bug in macOS implementation of sigaltstack
233
+ // UNIX2003 which returns ENOMEM when disabling a stack while
234
+ // passing ss_size smaller than MINSIGSTKSZ. According to POSIX
235
+ // both ss_sp and ss_size should be ignored in this case.
236
+ ss_size : sigstack_size,
237
+ } ;
238
+ unsafe { sigaltstack ( & disabling_stack, ptr:: null_mut ( ) ) } ;
239
+
240
+ // SAFETY: we created this mapping in `setup_sigaltstack` above with
241
+ // this exact size.
242
+ unsafe { munmap ( allocation. cast ( ) , sigstack_size + page_size) } ;
264
243
}
265
244
266
245
/// Modern kernels on modern hardware can have dynamic signal stack sizes.
@@ -577,13 +556,6 @@ mod imp {
577
556
target_os = "illumos" ,
578
557
) ) ) ]
579
558
mod imp {
580
- pub unsafe fn init ( ) { }
581
-
582
- pub unsafe fn cleanup ( ) { }
583
-
584
- pub unsafe fn make_handler ( _main_thread : bool ) -> super :: Handler {
585
- super :: Handler :: null ( )
586
- }
587
-
588
- pub unsafe fn drop_handler ( _data : * mut libc:: c_void ) { }
559
+ pub unsafe fn protect ( _main_thread : bool ) { }
560
+ pub fn cleanup ( ) { }
589
561
}
0 commit comments