Skip to content

Commit bd26295

Browse files
authored
Rollup merge of rust-lang#127843 - workingjubilee:break-up-big-ass-stack-overflow-fn, r=joboet
unix: document unsafety for std `sig{action,altstack}` I found many surprising elements here while trying to wrap a measly 5 functions with `unsafe`. I would rather not "just" mindlessly wrap this code with `unsafe { }`, so I decided to document it properly. On Unix, this code covers the "create and setup signal handler" part of the stack overflow code, and serves as the primary safety boundary for the signal handler. It is rarely audited, very gnarly, and worth extra attention. It calls other unsafe functions defined in this module, but "can we correctly map the right memory, or find the right address ranges?" are separate questions, and get increasingly platform-specific. The question here is the more general "are we doing everything in the correct order, and setting up the handler in the correct way?" As part of this audit, I noticed that we do some peculiar things that we should probably refrain from. However, I avoided making changes that I deemed might have a different final result in Rust programs. I did, however, reorder some events so that the signal handler is installed _after_ we install the alternate stack. We do not run much code between these events, but it is probably best if the timespan between the handler being available and the new stack being installed is 0 nanoseconds.
2 parents 2b62867 + 489f1ef commit bd26295

File tree

1 file changed

+61
-24
lines changed

1 file changed

+61
-24
lines changed

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

+61-24
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,18 @@ mod imp {
8787
// out many large systems and all implementations allow returning from a
8888
// signal handler to work. For a more detailed explanation see the
8989
// comments on #26458.
90+
/// SIGSEGV/SIGBUS entry point
91+
/// # Safety
92+
/// Rust doesn't call this, it *gets called*.
93+
#[forbid(unsafe_op_in_unsafe_fn)]
9094
unsafe extern "C" fn signal_handler(
9195
signum: libc::c_int,
9296
info: *mut libc::siginfo_t,
9397
_data: *mut libc::c_void,
9498
) {
9599
let (start, end) = GUARD.get();
96-
let addr = (*info).si_addr() as usize;
100+
// SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`.
101+
let addr = unsafe { (*info).si_addr().addr() };
97102

98103
// If the faulting address is within the guard page, then we print a
99104
// message saying so and abort.
@@ -105,9 +110,11 @@ mod imp {
105110
rtabort!("stack overflow");
106111
} else {
107112
// Unregister ourselves by reverting back to the default behavior.
108-
let mut action: sigaction = mem::zeroed();
113+
// SAFETY: assuming all platforms define struct sigaction as "zero-initializable"
114+
let mut action: sigaction = unsafe { mem::zeroed() };
109115
action.sa_sigaction = SIG_DFL;
110-
sigaction(signum, &action, ptr::null_mut());
116+
// SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction
117+
unsafe { sigaction(signum, &action, ptr::null_mut()) };
111118

112119
// See comment above for why this function returns.
113120
}
@@ -117,32 +124,45 @@ mod imp {
117124
static MAIN_ALTSTACK: AtomicPtr<libc::c_void> = AtomicPtr::new(ptr::null_mut());
118125
static NEED_ALTSTACK: AtomicBool = AtomicBool::new(false);
119126

127+
/// # Safety
128+
/// Must be called only once
129+
#[forbid(unsafe_op_in_unsafe_fn)]
120130
pub unsafe fn init() {
121131
PAGE_SIZE.store(os::page_size(), Ordering::Relaxed);
122132

123133
// Always write to GUARD to ensure the TLS variable is allocated.
124-
let guard = install_main_guard().unwrap_or(0..0);
134+
let guard = unsafe { install_main_guard().unwrap_or(0..0) };
125135
GUARD.set((guard.start, guard.end));
126136

127-
let mut action: sigaction = mem::zeroed();
137+
// SAFETY: assuming all platforms define struct sigaction as "zero-initializable"
138+
let mut action: sigaction = unsafe { mem::zeroed() };
128139
for &signal in &[SIGSEGV, SIGBUS] {
129-
sigaction(signal, ptr::null_mut(), &mut action);
140+
// SAFETY: just fetches the current signal handler into action
141+
unsafe { sigaction(signal, ptr::null_mut(), &mut action) };
130142
// Configure our signal handler if one is not already set.
131143
if action.sa_sigaction == SIG_DFL {
144+
if !NEED_ALTSTACK.load(Ordering::Relaxed) {
145+
// haven't set up our sigaltstack yet
146+
NEED_ALTSTACK.store(true, Ordering::Release);
147+
let handler = unsafe { make_handler(true) };
148+
MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed);
149+
mem::forget(handler);
150+
}
132151
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
133152
action.sa_sigaction = signal_handler as sighandler_t;
134-
sigaction(signal, &action, ptr::null_mut());
135-
NEED_ALTSTACK.store(true, Ordering::Relaxed);
153+
// SAFETY: only overriding signals if the default is set
154+
unsafe { sigaction(signal, &action, ptr::null_mut()) };
136155
}
137156
}
138-
139-
let handler = make_handler(true);
140-
MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed);
141-
mem::forget(handler);
142157
}
143158

159+
/// # Safety
160+
/// Must be called only once
161+
#[forbid(unsafe_op_in_unsafe_fn)]
144162
pub unsafe fn cleanup() {
145-
drop_handler(MAIN_ALTSTACK.load(Ordering::Relaxed));
163+
// FIXME: I probably cause more bugs than I'm worth!
164+
// see https://github.com/rust-lang/rust/issues/111272
165+
unsafe { drop_handler(MAIN_ALTSTACK.load(Ordering::Relaxed)) };
146166
}
147167

148168
unsafe fn get_stack() -> libc::stack_t {
@@ -187,34 +207,48 @@ mod imp {
187207
libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size }
188208
}
189209

210+
/// # Safety
211+
/// Mutates the alternate signal stack
212+
#[forbid(unsafe_op_in_unsafe_fn)]
190213
pub unsafe fn make_handler(main_thread: bool) -> Handler {
191-
if !NEED_ALTSTACK.load(Ordering::Relaxed) {
214+
if !NEED_ALTSTACK.load(Ordering::Acquire) {
192215
return Handler::null();
193216
}
194217

195218
if !main_thread {
196219
// Always write to GUARD to ensure the TLS variable is allocated.
197-
let guard = current_guard().unwrap_or(0..0);
220+
let guard = unsafe { current_guard() }.unwrap_or(0..0);
198221
GUARD.set((guard.start, guard.end));
199222
}
200223

201-
let mut stack = mem::zeroed();
202-
sigaltstack(ptr::null(), &mut stack);
224+
// SAFETY: assuming stack_t is zero-initializable
225+
let mut stack = unsafe { mem::zeroed() };
226+
// SAFETY: reads current stack_t into stack
227+
unsafe { sigaltstack(ptr::null(), &mut stack) };
203228
// Configure alternate signal stack, if one is not already set.
204229
if stack.ss_flags & SS_DISABLE != 0 {
205-
stack = get_stack();
206-
sigaltstack(&stack, ptr::null_mut());
230+
// SAFETY: We warned our caller this would happen!
231+
unsafe {
232+
stack = get_stack();
233+
sigaltstack(&stack, ptr::null_mut());
234+
}
207235
Handler { data: stack.ss_sp as *mut libc::c_void }
208236
} else {
209237
Handler::null()
210238
}
211239
}
212240

241+
/// # Safety
242+
/// Must be called
243+
/// - only with our handler or nullptr
244+
/// - only when done with our altstack
245+
/// This disables the alternate signal stack!
246+
#[forbid(unsafe_op_in_unsafe_fn)]
213247
pub unsafe fn drop_handler(data: *mut libc::c_void) {
214248
if !data.is_null() {
215249
let sigstack_size = sigstack_size();
216250
let page_size = PAGE_SIZE.load(Ordering::Relaxed);
217-
let stack = libc::stack_t {
251+
let disabling_stack = libc::stack_t {
218252
ss_sp: ptr::null_mut(),
219253
ss_flags: SS_DISABLE,
220254
// Workaround for bug in macOS implementation of sigaltstack
@@ -223,10 +257,11 @@ mod imp {
223257
// both ss_sp and ss_size should be ignored in this case.
224258
ss_size: sigstack_size,
225259
};
226-
sigaltstack(&stack, ptr::null_mut());
227-
// We know from `get_stackp` that the alternate stack we installed is part of a mapping
228-
// that started one page earlier, so walk back a page and unmap from there.
229-
munmap(data.sub(page_size), sigstack_size + page_size);
260+
// SAFETY: we warned the caller this disables the alternate signal stack!
261+
unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) };
262+
// SAFETY: We know from `get_stackp` that the alternate stack we installed is part of
263+
// a mapping that started one page earlier, so walk back a page and unmap from there.
264+
unsafe { munmap(data.sub(page_size), sigstack_size + page_size) };
230265
}
231266
}
232267

@@ -455,6 +490,7 @@ mod imp {
455490
}
456491

457492
#[cfg(any(target_os = "macos", target_os = "openbsd", target_os = "solaris"))]
493+
// FIXME: I am probably not unsafe.
458494
unsafe fn current_guard() -> Option<Range<usize>> {
459495
let stackptr = get_stack_start()?;
460496
let stackaddr = stackptr.addr();
@@ -469,6 +505,7 @@ mod imp {
469505
target_os = "netbsd",
470506
target_os = "l4re"
471507
))]
508+
// FIXME: I am probably not unsafe.
472509
unsafe fn current_guard() -> Option<Range<usize>> {
473510
let mut ret = None;
474511
let mut attr: libc::pthread_attr_t = crate::mem::zeroed();

0 commit comments

Comments
 (0)