Skip to content

Commit 3be9ca1

Browse files
committed
Auto merge of #31333 - lambda:31273-abort-on-stack-overflow, r=brson
Abort on stack overflow instead of re-raising SIGSEGV We use guard pages that cause the process to abort to protect against undefined behavior in the event of stack overflow. We have a handler that catches segfaults, prints out an error message if the segfault was due to a stack overflow, then unregisters itself and returns to allow the signal to be re-raised and kill the process. This caused some confusion, as it was unexpected that safe code would be able to cause a segfault, while it's easy to overflow the stack in safe code. To avoid this confusion, when we detect a segfault in the guard page, abort instead of the previous behavior of re-raising SIGSEGV. To test this, we need to adapt the tests for segfault to actually check the exit status. Doing so revealed that the existing test for segfault behavior was actually invalid; LLVM optimizes the explicit null pointer reference down to an illegal instruction, so the program aborts with SIGILL instead of SIGSEGV and the test didn't actually trigger the signal handler at all. Use a C helper function to get a null pointer that LLVM can't optimize away, so we get our segfault instead. This is a [breaking-change] if anyone is relying on the exact signal raised to kill a process on stack overflow. Closes #31273
2 parents 34af2de + ee79bfa commit 3be9ca1

File tree

4 files changed

+74
-20
lines changed

4 files changed

+74
-20
lines changed

src/libstd/sys/unix/stack_overflow.rs

+21-17
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,19 @@ mod imp {
8484
// were originally supposed to do.
8585
//
8686
// This handler currently exists purely to print an informative message
87-
// whenever a thread overflows its stack. When run the handler always
88-
// un-registers itself after running and then returns (to allow the original
89-
// signal to be delivered again). By returning we're ensuring that segfaults
90-
// do indeed look like segfaults.
87+
// whenever a thread overflows its stack. We then abort to exit and
88+
// indicate a crash, but to avoid a misleading SIGSEGV that might lead
89+
// users to believe that unsafe code has accessed an invalid pointer; the
90+
// SIGSEGV encountered when overflowing the stack is expected and
91+
// well-defined.
9192
//
92-
// Returning from this kind of signal handler is technically not defined to
93-
// work when reading the POSIX spec strictly, but in practice it turns out
94-
// many large systems and all implementations allow returning from a signal
95-
// handler to work. For a more detailed explanation see the comments on
96-
// #26458.
93+
// If this is not a stack overflow, the handler un-registers itself and
94+
// then returns (to allow the original signal to be delivered again).
95+
// Returning from this kind of signal handler is technically not defined
96+
// to work when reading the POSIX spec strictly, but in practice it turns
97+
// out many large systems and all implementations allow returning from a
98+
// signal handler to work. For a more detailed explanation see the
99+
// comments on #26458.
97100
unsafe extern fn signal_handler(signum: libc::c_int,
98101
info: *mut libc::siginfo_t,
99102
_data: *mut libc::c_void) {
@@ -103,17 +106,18 @@ mod imp {
103106
let addr = siginfo_si_addr(info);
104107

105108
// If the faulting address is within the guard page, then we print a
106-
// message saying so.
109+
// message saying so and abort.
107110
if guard != 0 && guard - PAGE_SIZE <= addr && addr < guard {
108111
report_overflow();
112+
rtabort!("stack overflow");
113+
} else {
114+
// Unregister ourselves by reverting back to the default behavior.
115+
let mut action: sigaction = mem::zeroed();
116+
action.sa_sigaction = SIG_DFL;
117+
sigaction(signum, &action, ptr::null_mut());
118+
119+
// See comment above for why this function returns.
109120
}
110-
111-
// Unregister ourselves by reverting back to the default behavior.
112-
let mut action: sigaction = mem::zeroed();
113-
action.sa_sigaction = SIG_DFL;
114-
sigaction(signum, &action, ptr::null_mut());
115-
116-
// See comment above for why this function returns.
117121
}
118122

119123
static mut MAIN_ALTSTACK: *mut libc::c_void = ptr::null_mut();

src/rt/rust_test_helpers.c

+5
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ rust_get_test_int() {
158158
return 1;
159159
}
160160

161+
char *
162+
rust_get_null_ptr() {
163+
return 0;
164+
}
165+
161166
/* Debug helpers strictly to verify ABI conformance.
162167
*
163168
* FIXME (#2665): move these into a testcase when the testsuite

src/test/run-pass/out-of-stack.rs

+25-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
// ignore-musl
1313

1414
#![feature(asm)]
15+
#![feature(libc)]
16+
17+
#[cfg(unix)]
18+
extern crate libc;
1519

1620
use std::env;
1721
use std::process::Command;
@@ -35,6 +39,24 @@ fn loud_recurse() {
3539
black_box(()); // don't optimize this into a tail call. please.
3640
}
3741

42+
#[cfg(unix)]
43+
fn check_status(status: std::process::ExitStatus)
44+
{
45+
use libc;
46+
use std::os::unix::process::ExitStatusExt;
47+
48+
assert!(!status.success());
49+
assert!(status.signal() != Some(libc::SIGSEGV)
50+
&& status.signal() != Some(libc::SIGBUS));
51+
}
52+
53+
#[cfg(not(unix))]
54+
fn check_status(status: std::process::ExitStatus)
55+
{
56+
assert!(!status.success());
57+
}
58+
59+
3860
fn main() {
3961
let args: Vec<String> = env::args().collect();
4062
if args.len() > 1 && args[1] == "silent" {
@@ -62,7 +84,9 @@ fn main() {
6284
println!("testing: {}", mode);
6385

6486
let silent = Command::new(&args[0]).arg(mode).output().unwrap();
65-
assert!(!silent.status.success());
87+
88+
check_status(silent.status);
89+
6690
let error = String::from_utf8_lossy(&silent.stderr);
6791
assert!(error.contains("has overflowed its stack"),
6892
"missing overflow message: {}", error);

src/test/run-pass/segfault-no-out-of-stack.rs

+23-2
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,39 @@ extern crate libc;
1515
use std::process::{Command, ExitStatus};
1616
use std::env;
1717

18+
#[link(name = "rust_test_helpers")]
19+
extern {
20+
fn rust_get_null_ptr() -> *mut ::libc::c_char;
21+
}
22+
23+
#[cfg(unix)]
24+
fn check_status(status: std::process::ExitStatus)
25+
{
26+
use libc;
27+
use std::os::unix::process::ExitStatusExt;
28+
29+
assert!(status.signal() == Some(libc::SIGSEGV)
30+
|| status.signal() == Some(libc::SIGBUS));
31+
}
32+
33+
#[cfg(not(unix))]
34+
fn check_status(status: std::process::ExitStatus)
35+
{
36+
assert!(!status.success());
37+
}
38+
1839
fn main() {
1940
let args: Vec<String> = env::args().collect();
2041
if args.len() > 1 && args[1] == "segfault" {
21-
unsafe { *(0 as *mut isize) = 1 }; // trigger a segfault
42+
unsafe { *rust_get_null_ptr() = 1; }; // trigger a segfault
2243
} else {
2344
let segfault = Command::new(&args[0]).arg("segfault").output().unwrap();
2445
let stderr = String::from_utf8_lossy(&segfault.stderr);
2546
let stdout = String::from_utf8_lossy(&segfault.stdout);
2647
println!("stdout: {}", stdout);
2748
println!("stderr: {}", stderr);
2849
println!("status: {}", segfault.status);
29-
assert!(!segfault.status.success());
50+
check_status(segfault.status);
3051
assert!(!stderr.contains("has overflowed its stack"));
3152
}
3253
}

0 commit comments

Comments
 (0)