Skip to content

Commit 6620e2b

Browse files
committed
std: detect stack overflows in TLS destructors on UNIX
Fixes rust-lang#111272. With rust-lang#127912 merged, we now have all the infrastructure in place to support stack overflow detection in TLS destructors. This was not possible before because the signal stack was freed in the thread main function, thus a SIGSEGV afterwards would immediately crash. And on platforms without native TLS, the guard page address was stored in an allocation freed in a TLS destructor, so would not be available. rust-lang#127912 introduced the `local_pointer` macro which allows storing a pointer-sized TLS variable without allocation and the `thread_cleanup` runtime function which is called after all other code managed by the Rust runtime. This PR simply moves the signal stack cleanup to the end of `thread_cleanup` and uses `local_pointer` to store every necessary variable. And so, everything run under the Rust runtime is now properly protected against stack overflows.
1 parent af952c1 commit 6620e2b

File tree

13 files changed

+192
-173
lines changed

13 files changed

+192
-173
lines changed

library/std/src/rt.rs

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ pub(crate) fn thread_cleanup() {
117117
// print a nice message.
118118
panic::catch_unwind(|| {
119119
crate::thread::drop_current();
120+
crate::sys::thread_cleanup();
120121
})
121122
.unwrap_or_else(handle_rt_panic);
122123
}

library/std/src/sys/pal/hermit/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {
6969
}
7070
}
7171

72+
pub fn thread_cleanup() {}
73+
7274
// SAFETY: must be called only once during runtime cleanup.
7375
// NOTE: this is not guaranteed to run, for example when the program aborts.
7476
pub unsafe fn cleanup() {}

library/std/src/sys/pal/sgx/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {
3737
}
3838
}
3939

40+
pub fn thread_cleanup() {}
41+
4042
// SAFETY: must be called only once during runtime cleanup.
4143
// NOTE: this is not guaranteed to run, for example when the program aborts.
4244
pub unsafe fn cleanup() {}

library/std/src/sys/pal/solid/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ pub mod time;
3838
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
3939
pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {}
4040

41+
pub fn thread_cleanup() {}
42+
4143
// SAFETY: must be called only once during runtime cleanup.
4244
pub unsafe fn cleanup() {}
4345

library/std/src/sys/pal/teeos/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ pub fn abort_internal() -> ! {
4545
// so this should never be called.
4646
pub fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {}
4747

48+
pub fn thread_cleanup() {}
49+
4850
// SAFETY: must be called only once during runtime cleanup.
4951
// this is not guaranteed to run, for example when the program aborts.
5052
pub unsafe fn cleanup() {

library/std/src/sys/pal/uefi/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ pub(crate) unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {
7676
}
7777
}
7878

79+
pub fn thread_cleanup() {}
80+
7981
/// # SAFETY
8082
/// this is not guaranteed to run, for example when the program aborts.
8183
/// - must be called only once during runtime cleanup.

library/std/src/sys/pal/unix/mod.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
5555
// behavior.
5656
reset_sigpipe(sigpipe);
5757

58-
stack_overflow::init();
58+
stack_overflow::protect(true);
5959
args::init(argc, argv);
6060

6161
// Normally, `thread::spawn` will call `Thread::set_name` but since this thread
@@ -229,12 +229,14 @@ pub(crate) fn on_broken_pipe_flag_used() -> bool {
229229
ON_BROKEN_PIPE_FLAG_USED.load(crate::sync::atomic::Ordering::Relaxed)
230230
}
231231

232-
// SAFETY: must be called only once during runtime cleanup.
233-
// NOTE: this is not guaranteed to run, for example when the program aborts.
234-
pub unsafe fn cleanup() {
232+
pub fn thread_cleanup() {
235233
stack_overflow::cleanup();
236234
}
237235

236+
// SAFETY: must be called only once during runtime cleanup.
237+
// NOTE: this is not guaranteed to run, for example when the program aborts.
238+
pub unsafe fn cleanup() {}
239+
238240
#[allow(unused_imports)]
239241
pub use libc::signal;
240242

library/std/src/sys/pal/unix/stack_overflow.rs

+109-137
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,6 @@
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
)))]
579558
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() {}
589561
}

0 commit comments

Comments
 (0)