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,24 @@ 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} ;
30+ use crate :: thread:: Thread ;
5431 use crate :: { io, mem, ptr, thread} ;
5532
5633 // We use a TLS variable to store the address of the guard page. While TLS
5734 // variables are not guaranteed to be signal-safe, this works out in practice
5835 // since we make sure to write to the variable before the signal stack is
5936 // installed, thereby ensuring that the variable is always allocated when
6037 // 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 ;
6443 }
6544
6645 // Signal handler for the SIGSEGV and SIGBUS handlers. We've got guard pages
@@ -93,7 +72,9 @@ mod imp {
9372 info : * mut libc:: siginfo_t ,
9473 _data : * mut libc:: c_void ,
9574 ) {
96- let ( start, end) = GUARD . get ( ) ;
75+ let start = GUARD_START . get ( ) . addr ( ) ;
76+ let end = GUARD_END . get ( ) . addr ( ) ;
77+
9778 // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`.
9879 let addr = unsafe { ( * info) . si_addr ( ) . addr ( ) } ;
9980
@@ -119,51 +100,72 @@ mod imp {
119100 }
120101
121102 static PAGE_SIZE : AtomicUsize = AtomicUsize :: new ( 0 ) ;
122- static MAIN_ALTSTACK : AtomicPtr < libc:: c_void > = AtomicPtr :: new ( ptr:: null_mut ( ) ) ;
123103 static NEED_ALTSTACK : AtomicBool = AtomicBool :: new ( false ) ;
124104
105+ /// Set up stack overflow protection for the current thread
106+ ///
125107 /// # Safety
126- /// Must be called only once
108+ /// May only be called once per thread.
127109 #[ 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 ( ) ) } ;
148149 }
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 ( ) ) } ;
153150 }
151+ } else {
152+ unsafe { setup_sigaltstack ( ) } ;
154153 }
155154 }
156155
157156 /// # Safety
158- /// Must be called only once
157+ /// Mutates the alternate signal stack
159158 #[ 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+ }
165168
166- unsafe fn get_stack ( ) -> libc:: stack_t {
167169 // OpenBSD requires this flag for stack mapping
168170 // otherwise the said mapping will fail as a no-op on most systems
169171 // and has a different meaning on FreeBSD
@@ -185,82 +187,60 @@ mod imp {
185187 let sigstack_size = sigstack_size ( ) ;
186188 let page_size = PAGE_SIZE . load ( Ordering :: Relaxed ) ;
187189
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 {
197194 panic ! ( "failed to allocate an alternative stack: {}" , io:: Error :: last_os_error( ) ) ;
198195 }
199- let guard_result = libc:: mprotect ( stackp, page_size, PROT_NONE ) ;
196+
197+ let guard_result = unsafe { libc:: mprotect ( allocation, page_size, PROT_NONE ) } ;
200198 if guard_result != 0 {
201199 panic ! ( "failed to set up alternative stack guard page: {}" , io:: Error :: last_os_error( ) ) ;
202200 }
203- let stackp = stackp. add ( page_size) ;
204201
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 ( ) ) ;
206217 }
207218
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 ;
214223 }
215224
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 ( ) ) ;
221226
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 ) ;
238229
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) } ;
264244 }
265245
266246 /// Modern kernels on modern hardware can have dynamic signal stack sizes.
@@ -577,13 +557,6 @@ mod imp {
577557 target_os = "illumos" ,
578558) ) ) ]
579559mod 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 ( ) { }
589562}
0 commit comments