11#![ cfg_attr( test, allow( dead_code) ) ]
22
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} ;
274
285#[ cfg( any(
296 target_os = "linux" ,
@@ -45,22 +22,23 @@ mod imp {
4522 #[ cfg( all( target_os = "linux" , target_env = "gnu" ) ) ]
4623 use libc:: { mmap64, mprotect, munmap} ;
4724
48- use super :: Handler ;
49- use crate :: cell:: Cell ;
5025 use crate :: ops:: Range ;
5126 use crate :: sync:: OnceLock ;
52- use crate :: sync:: atomic:: { AtomicBool , AtomicPtr , AtomicUsize , Ordering } ;
27+ use crate :: sync:: atomic:: { AtomicBool , AtomicUsize , Ordering } ;
5328 use crate :: sys:: pal:: unix:: os;
29+ use crate :: sys:: thread_local:: { guard, local_pointer} ;
5430 use crate :: { io, mem, ptr, thread} ;
5531
5632 // We use a TLS variable to store the address of the guard page. While TLS
5733 // variables are not guaranteed to be signal-safe, this works out in practice
5834 // since we make sure to write to the variable before the signal stack is
5935 // installed, thereby ensuring that the variable is always allocated when
6036 // 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 ;
6442 }
6543
6644 // Signal handler for the SIGSEGV and SIGBUS handlers. We've got guard pages
@@ -93,7 +71,9 @@ mod imp {
9371 info : * mut libc:: siginfo_t ,
9472 _data : * mut libc:: c_void ,
9573 ) {
96- let ( start, end) = GUARD . get ( ) ;
74+ let start = GUARD_START . get ( ) . addr ( ) ;
75+ let end = GUARD_END . get ( ) . addr ( ) ;
76+
9777 // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`.
9878 let addr = unsafe { ( * info) . si_addr ( ) . addr ( ) } ;
9979
@@ -119,51 +99,72 @@ mod imp {
11999 }
120100
121101 static PAGE_SIZE : AtomicUsize = AtomicUsize :: new ( 0 ) ;
122- static MAIN_ALTSTACK : AtomicPtr < libc:: c_void > = AtomicPtr :: new ( ptr:: null_mut ( ) ) ;
123102 static NEED_ALTSTACK : AtomicBool = AtomicBool :: new ( false ) ;
124103
104+ /// Set up stack overflow protection for the current thread
105+ ///
125106 /// # Safety
126- /// Must be called only once
107+ /// May only be called once per thread.
127108 #[ 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 ( ) ) } ;
148148 }
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 ( ) ) } ;
153149 }
150+ } else {
151+ unsafe { setup_sigaltstack ( ) } ;
154152 }
155153 }
156154
157155 /// # Safety
158- /// Must be called only once
156+ /// Mutates the alternate signal stack
159157 #[ 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+ }
165167
166- unsafe fn get_stack ( ) -> libc:: stack_t {
167168 // OpenBSD requires this flag for stack mapping
168169 // otherwise the said mapping will fail as a no-op on most systems
169170 // and has a different meaning on FreeBSD
@@ -185,82 +186,60 @@ mod imp {
185186 let sigstack_size = sigstack_size ( ) ;
186187 let page_size = PAGE_SIZE . load ( Ordering :: Relaxed ) ;
187188
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 {
197193 panic ! ( "failed to allocate an alternative stack: {}" , io:: Error :: last_os_error( ) ) ;
198194 }
199- let guard_result = libc:: mprotect ( stackp, page_size, PROT_NONE ) ;
195+
196+ let guard_result = unsafe { libc:: mprotect ( allocation, page_size, PROT_NONE ) } ;
200197 if guard_result != 0 {
201198 panic ! ( "failed to set up alternative stack guard page: {}" , io:: Error :: last_os_error( ) ) ;
202199 }
203- let stackp = stackp. add ( page_size) ;
204200
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 ( ) ) ;
206216 }
207217
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 ;
214222 }
215223
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 ( ) ) ;
221225
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 ) ;
238228
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) } ;
264243 }
265244
266245 /// Modern kernels on modern hardware can have dynamic signal stack sizes.
@@ -577,13 +556,6 @@ mod imp {
577556 target_os = "illumos" ,
578557) ) ) ]
579558mod 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 ( ) { }
589561}
0 commit comments