Skip to content

Commit 83152c6

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 83152c6

File tree

13 files changed

+193
-173
lines changed

13 files changed

+193
-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

+110-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,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
)))]
579559
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() {}
589562
}

0 commit comments

Comments
 (0)