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,24 @@ 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} ;
30
+ use crate :: thread:: Thread ;
54
31
use crate :: { io, mem, ptr, thread} ;
55
32
56
33
// We use a TLS variable to store the address of the guard page. While TLS
57
34
// variables are not guaranteed to be signal-safe, this works out in practice
58
35
// since we make sure to write to the variable before the signal stack is
59
36
// installed, thereby ensuring that the variable is always allocated when
60
37
// 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 ) ) } ;
38
+ local_pointer ! {
39
+ static GUARD_START ;
40
+ static GUARD_END ;
41
+
42
+ static SIGALTSTACK ;
64
43
}
65
44
66
45
// Signal handler for the SIGSEGV and SIGBUS handlers. We've got guard pages
@@ -93,7 +72,9 @@ mod imp {
93
72
info : * mut libc:: siginfo_t ,
94
73
_data : * mut libc:: c_void ,
95
74
) {
96
- let ( start, end) = GUARD . get ( ) ;
75
+ let start = GUARD_START . get ( ) . addr ( ) ;
76
+ let end = GUARD_END . get ( ) . addr ( ) ;
77
+
97
78
// SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`.
98
79
let addr = unsafe { ( * info) . si_addr ( ) . addr ( ) } ;
99
80
@@ -119,51 +100,72 @@ mod imp {
119
100
}
120
101
121
102
static PAGE_SIZE : AtomicUsize = AtomicUsize :: new ( 0 ) ;
122
- static MAIN_ALTSTACK : AtomicPtr < libc:: c_void > = AtomicPtr :: new ( ptr:: null_mut ( ) ) ;
123
103
static NEED_ALTSTACK : AtomicBool = AtomicBool :: new ( false ) ;
124
104
105
+ /// Set up stack overflow protection for the current thread
106
+ ///
125
107
/// # Safety
126
- /// Must be called only once
108
+ /// May only be called once per thread.
127
109
#[ 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) ;
110
+ pub unsafe fn protect ( main_thread : bool ) {
111
+ if main_thread {
112
+ PAGE_SIZE . store ( os:: page_size ( ) , Ordering :: Relaxed ) ;
113
+ // Use acquire ordering to observe the page size store above,
114
+ // which is propagated by a release store to NEED_ALTSTACK.
115
+ } else if !NEED_ALTSTACK . load ( Ordering :: Acquire ) {
116
+ return ;
117
+ }
118
+
119
+ let guard = if main_thread {
120
+ unsafe { install_main_guard ( ) . unwrap_or ( 0 ..0 ) }
121
+ } else {
122
+ unsafe { current_guard ( ) . unwrap_or ( 0 ..0 ) }
123
+ } ;
124
+
125
+ // Always store the guard range to ensure the TLS variables are allocated.
126
+ GUARD_START . set ( ptr:: without_provenance_mut ( guard. start ) ) ;
127
+ GUARD_END . set ( ptr:: without_provenance_mut ( guard. end ) ) ;
128
+
129
+ if main_thread {
130
+ // SAFETY: assuming all platforms define struct sigaction as "zero-initializable"
131
+ let mut action: sigaction = unsafe { mem:: zeroed ( ) } ;
132
+ for & signal in & [ SIGSEGV , SIGBUS ] {
133
+ // SAFETY: just fetches the current signal handler into action
134
+ unsafe { sigaction ( signal, ptr:: null_mut ( ) , & mut action) } ;
135
+ // Configure our signal handler if one is not already set.
136
+ if action. sa_sigaction == SIG_DFL {
137
+ if !NEED_ALTSTACK . load ( Ordering :: Relaxed ) {
138
+ // Set up the signal stack and tell other threads to set
139
+ // up their own. This uses a release store to propagate
140
+ // the store to PAGE_SIZE above.
141
+ NEED_ALTSTACK . store ( true , Ordering :: Release ) ;
142
+ unsafe { setup_sigaltstack ( ) } ;
143
+ }
144
+
145
+ action. sa_flags = SA_SIGINFO | SA_ONSTACK ;
146
+ action. sa_sigaction = signal_handler as sighandler_t ;
147
+ // SAFETY: only overriding signals if the default is set
148
+ unsafe { sigaction ( signal, & action, ptr:: null_mut ( ) ) } ;
148
149
}
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
150
}
151
+ } else {
152
+ unsafe { setup_sigaltstack ( ) } ;
154
153
}
155
154
}
156
155
157
156
/// # Safety
158
- /// Must be called only once
157
+ /// Mutates the alternate signal stack
159
158
#[ 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
- }
159
+ unsafe fn setup_sigaltstack ( ) {
160
+ // SAFETY: assuming stack_t is zero-initializable
161
+ let mut stack = unsafe { mem:: zeroed ( ) } ;
162
+ // SAFETY: reads current stack_t into stack
163
+ unsafe { sigaltstack ( ptr:: null ( ) , & mut stack) } ;
164
+ // Do not overwrite the stack if one is already set.
165
+ if stack. ss_flags & SS_DISABLE == 0 {
166
+ return ;
167
+ }
165
168
166
- unsafe fn get_stack ( ) -> libc:: stack_t {
167
169
// OpenBSD requires this flag for stack mapping
168
170
// otherwise the said mapping will fail as a no-op on most systems
169
171
// and has a different meaning on FreeBSD
@@ -185,82 +187,60 @@ mod imp {
185
187
let sigstack_size = sigstack_size ( ) ;
186
188
let page_size = PAGE_SIZE . load ( Ordering :: Relaxed ) ;
187
189
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 {
190
+ let allocation = unsafe {
191
+ mmap64 ( ptr:: null_mut ( ) , sigstack_size + page_size, PROT_READ | PROT_WRITE , flags, -1 , 0 )
192
+ } ;
193
+ if allocation == MAP_FAILED {
197
194
panic ! ( "failed to allocate an alternative stack: {}" , io:: Error :: last_os_error( ) ) ;
198
195
}
199
- let guard_result = libc:: mprotect ( stackp, page_size, PROT_NONE ) ;
196
+
197
+ let guard_result = unsafe { libc:: mprotect ( allocation, page_size, PROT_NONE ) } ;
200
198
if guard_result != 0 {
201
199
panic ! ( "failed to set up alternative stack guard page: {}" , io:: Error :: last_os_error( ) ) ;
202
200
}
203
- let stackp = stackp. add ( page_size) ;
204
201
205
- libc:: stack_t { ss_sp : stackp, ss_flags : 0 , ss_size : sigstack_size }
202
+ let stack = libc:: stack_t {
203
+ // Reserve a guard page at the bottom of the allocation.
204
+ ss_sp : unsafe { allocation. add ( page_size) } ,
205
+ ss_flags : 0 ,
206
+ ss_size : sigstack_size,
207
+ } ;
208
+ // SAFETY: We warned our caller this would happen!
209
+ unsafe {
210
+ sigaltstack ( & stack, ptr:: null_mut ( ) ) ;
211
+ }
212
+
213
+ // Ensure that `rt::thread_cleanup` gets called, which will in turn call
214
+ // cleanup, where this signal stack will be freed.
215
+ guard:: enable ( ) ;
216
+ SIGALTSTACK . set ( allocation. cast ( ) ) ;
206
217
}
207
218
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 ( ) ;
219
+ pub fn cleanup ( ) {
220
+ let allocation = SIGALTSTACK . get ( ) ;
221
+ if allocation. is_null ( ) {
222
+ return ;
214
223
}
215
224
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
- }
225
+ SIGALTSTACK . set ( ptr:: null_mut ( ) ) ;
221
226
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
- }
227
+ let sigstack_size = sigstack_size ( ) ;
228
+ let page_size = PAGE_SIZE . load ( Ordering :: Relaxed ) ;
238
229
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
- }
230
+ let disabling_stack = libc:: stack_t {
231
+ ss_sp : ptr:: null_mut ( ) ,
232
+ ss_flags : SS_DISABLE ,
233
+ // Workaround for bug in macOS implementation of sigaltstack
234
+ // UNIX2003 which returns ENOMEM when disabling a stack while
235
+ // passing ss_size smaller than MINSIGSTKSZ. According to POSIX
236
+ // both ss_sp and ss_size should be ignored in this case.
237
+ ss_size : sigstack_size,
238
+ } ;
239
+ unsafe { sigaltstack ( & disabling_stack, ptr:: null_mut ( ) ) } ;
240
+
241
+ // SAFETY: we created this mapping in `setup_sigaltstack` above with
242
+ // this exact size.
243
+ unsafe { munmap ( allocation. cast ( ) , sigstack_size + page_size) } ;
264
244
}
265
245
266
246
/// Modern kernels on modern hardware can have dynamic signal stack sizes.
@@ -577,13 +557,6 @@ mod imp {
577
557
target_os = "illumos" ,
578
558
) ) ) ]
579
559
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 ) { }
560
+ pub unsafe fn protect ( _main_thread : bool ) { }
561
+ pub fn cleanup ( ) { }
589
562
}
0 commit comments