Skip to content

Commit 55b54a9

Browse files
committed
Use a range to identify SIGSEGV in stack guards
Previously, the `guard::init()` and `guard::current()` functions were returning a `usize` address representing the top of the stack guard, respectively for the main thread and for spawned threads. The `SIGSEGV` handler on `unix` targets checked if a fault was within one page below that address, if so reporting it as a stack overflow. Now `unix` targets report a `Range<usize>` representing the guard memory, so it can cover arbitrary guard sizes. Non-`unix` targets which always return `None` for guards now do so with `Option<!>`, so they don't pay any overhead. For `linux-gnu` in particular, the previous guard upper-bound was `stackaddr + guardsize`, as the protected memory was *inside* the stack. This was a glibc bug, and starting from 2.27 they are moving the guard *past* the end of the stack. However, there's no simple way for us to know where the guard page actually lies, so now we declare it as the whole range of `stackaddr ± guardsize`, and any fault therein will be called a stack overflow. This fixes #47863.
1 parent e2de8de commit 55b54a9

File tree

7 files changed

+89
-64
lines changed

7 files changed

+89
-64
lines changed

Diff for: src/libstd/sys/cloudabi/thread.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,11 @@ impl Drop for Thread {
111111

112112
#[cfg_attr(test, allow(dead_code))]
113113
pub mod guard {
114-
pub unsafe fn current() -> Option<usize> {
114+
pub type Guard = !;
115+
pub unsafe fn current() -> Option<Guard> {
115116
None
116117
}
117-
pub unsafe fn init() -> Option<usize> {
118+
pub unsafe fn init() -> Option<Guard> {
118119
None
119120
}
120121
}

Diff for: src/libstd/sys/redox/thread.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ impl Thread {
8888
}
8989

9090
pub mod guard {
91-
pub unsafe fn current() -> Option<usize> { None }
92-
pub unsafe fn init() -> Option<usize> { None }
91+
pub type Guard = !;
92+
pub unsafe fn current() -> Option<Guard> { None }
93+
pub unsafe fn init() -> Option<Guard> { None }
9394
}

Diff for: src/libstd/sys/unix/stack_overflow.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ mod imp {
5757
use sys_common::thread_info;
5858

5959

60-
// This is initialized in init() and only read from after
61-
static mut PAGE_SIZE: usize = 0;
62-
6360
#[cfg(any(target_os = "linux", target_os = "android"))]
6461
unsafe fn siginfo_si_addr(info: *mut libc::siginfo_t) -> usize {
6562
#[repr(C)]
@@ -102,12 +99,12 @@ mod imp {
10299
_data: *mut libc::c_void) {
103100
use sys_common::util::report_overflow;
104101

105-
let guard = thread_info::stack_guard().unwrap_or(0);
102+
let guard = thread_info::stack_guard().unwrap_or(0..0);
106103
let addr = siginfo_si_addr(info);
107104

108105
// If the faulting address is within the guard page, then we print a
109106
// message saying so and abort.
110-
if guard != 0 && guard - PAGE_SIZE <= addr && addr < guard {
107+
if guard.start <= addr && addr < guard.end {
111108
report_overflow();
112109
rtabort!("stack overflow");
113110
} else {
@@ -123,8 +120,6 @@ mod imp {
123120
static mut MAIN_ALTSTACK: *mut libc::c_void = ptr::null_mut();
124121

125122
pub unsafe fn init() {
126-
PAGE_SIZE = ::sys::os::page_size();
127-
128123
let mut action: sigaction = mem::zeroed();
129124
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
130125
action.sa_sigaction = signal_handler as sighandler_t;

Diff for: src/libstd/sys/unix/thread.rs

+70-45
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,10 @@ impl Drop for Thread {
205205
not(target_os = "solaris")))]
206206
#[cfg_attr(test, allow(dead_code))]
207207
pub mod guard {
208-
pub unsafe fn current() -> Option<usize> { None }
209-
pub unsafe fn init() -> Option<usize> { None }
208+
use ops::Range;
209+
pub type Guard = Range<usize>;
210+
pub unsafe fn current() -> Option<Guard> { None }
211+
pub unsafe fn init() -> Option<Guard> { None }
210212
}
211213

212214

@@ -222,14 +224,43 @@ pub mod guard {
222224
use libc;
223225
use libc::mmap;
224226
use libc::{PROT_NONE, MAP_PRIVATE, MAP_ANON, MAP_FAILED, MAP_FIXED};
227+
use ops::Range;
225228
use sys::os;
226229

227-
#[cfg(any(target_os = "macos",
228-
target_os = "bitrig",
229-
target_os = "openbsd",
230-
target_os = "solaris"))]
230+
// This is initialized in init() and only read from after
231+
static mut PAGE_SIZE: usize = 0;
232+
233+
pub type Guard = Range<usize>;
234+
235+
#[cfg(target_os = "solaris")]
236+
unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
237+
let mut current_stack: libc::stack_t = ::mem::zeroed();
238+
assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
239+
Some(current_stack.ss_sp)
240+
}
241+
242+
#[cfg(target_os = "macos")]
231243
unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
232-
current().map(|s| s as *mut libc::c_void)
244+
let stackaddr = libc::pthread_get_stackaddr_np(libc::pthread_self()) as usize -
245+
libc::pthread_get_stacksize_np(libc::pthread_self());
246+
Some(stackaddr as *mut libc::c_void)
247+
}
248+
249+
#[cfg(any(target_os = "openbsd", target_os = "bitrig"))]
250+
unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
251+
let mut current_stack: libc::stack_t = ::mem::zeroed();
252+
assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(),
253+
&mut current_stack), 0);
254+
255+
let extra = if cfg!(target_os = "bitrig") {3} else {1} * PAGE_SIZE;
256+
let stackaddr = if libc::pthread_main_np() == 1 {
257+
// main thread
258+
current_stack.ss_sp as usize - current_stack.ss_size + extra
259+
} else {
260+
// new thread
261+
current_stack.ss_sp as usize - current_stack.ss_size
262+
};
263+
Some(stackaddr as *mut libc::c_void)
233264
}
234265

235266
#[cfg(any(target_os = "android", target_os = "freebsd",
@@ -253,8 +284,9 @@ pub mod guard {
253284
ret
254285
}
255286

256-
pub unsafe fn init() -> Option<usize> {
257-
let psize = os::page_size();
287+
pub unsafe fn init() -> Option<Guard> {
288+
PAGE_SIZE = os::page_size();
289+
258290
let mut stackaddr = get_stack_start()?;
259291

260292
// Ensure stackaddr is page aligned! A parent process might
@@ -263,9 +295,9 @@ pub mod guard {
263295
// stackaddr < stackaddr + stacksize, so if stackaddr is not
264296
// page-aligned, calculate the fix such that stackaddr <
265297
// new_page_aligned_stackaddr < stackaddr + stacksize
266-
let remainder = (stackaddr as usize) % psize;
298+
let remainder = (stackaddr as usize) % PAGE_SIZE;
267299
if remainder != 0 {
268-
stackaddr = ((stackaddr as usize) + psize - remainder)
300+
stackaddr = ((stackaddr as usize) + PAGE_SIZE - remainder)
269301
as *mut libc::c_void;
270302
}
271303

@@ -280,60 +312,42 @@ pub mod guard {
280312
// Instead, we'll just note where we expect rlimit to start
281313
// faulting, so our handler can report "stack overflow", and
282314
// trust that the kernel's own stack guard will work.
283-
Some(stackaddr as usize)
315+
let stackaddr = stackaddr as usize;
316+
Some(stackaddr - PAGE_SIZE..stackaddr)
284317
} else {
285318
// Reallocate the last page of the stack.
286319
// This ensures SIGBUS will be raised on
287320
// stack overflow.
288-
let result = mmap(stackaddr, psize, PROT_NONE,
321+
let result = mmap(stackaddr, PAGE_SIZE, PROT_NONE,
289322
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
290323

291324
if result != stackaddr || result == MAP_FAILED {
292325
panic!("failed to allocate a guard page");
293326
}
294327

328+
let guardaddr = stackaddr as usize;
295329
let offset = if cfg!(target_os = "freebsd") {
296330
2
297331
} else {
298332
1
299333
};
300334

301-
Some(stackaddr as usize + offset * psize)
335+
Some(guardaddr..guardaddr + offset * PAGE_SIZE)
302336
}
303337
}
304338

305-
#[cfg(target_os = "solaris")]
306-
pub unsafe fn current() -> Option<usize> {
307-
let mut current_stack: libc::stack_t = ::mem::zeroed();
308-
assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
309-
Some(current_stack.ss_sp as usize)
310-
}
311-
312-
#[cfg(target_os = "macos")]
313-
pub unsafe fn current() -> Option<usize> {
314-
Some(libc::pthread_get_stackaddr_np(libc::pthread_self()) as usize -
315-
libc::pthread_get_stacksize_np(libc::pthread_self()))
316-
}
317-
318-
#[cfg(any(target_os = "openbsd", target_os = "bitrig"))]
319-
pub unsafe fn current() -> Option<usize> {
320-
let mut current_stack: libc::stack_t = ::mem::zeroed();
321-
assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(),
322-
&mut current_stack), 0);
323-
324-
let extra = if cfg!(target_os = "bitrig") {3} else {1} * os::page_size();
325-
Some(if libc::pthread_main_np() == 1 {
326-
// main thread
327-
current_stack.ss_sp as usize - current_stack.ss_size + extra
328-
} else {
329-
// new thread
330-
current_stack.ss_sp as usize - current_stack.ss_size
331-
})
339+
#[cfg(any(target_os = "macos",
340+
target_os = "bitrig",
341+
target_os = "openbsd",
342+
target_os = "solaris"))]
343+
pub unsafe fn current() -> Option<Guard> {
344+
let stackaddr = get_stack_start()? as usize;
345+
Some(stackaddr - PAGE_SIZE..stackaddr)
332346
}
333347

334348
#[cfg(any(target_os = "android", target_os = "freebsd",
335349
target_os = "linux", target_os = "netbsd", target_os = "l4re"))]
336-
pub unsafe fn current() -> Option<usize> {
350+
pub unsafe fn current() -> Option<Guard> {
337351
let mut ret = None;
338352
let mut attr: libc::pthread_attr_t = ::mem::zeroed();
339353
assert_eq!(libc::pthread_attr_init(&mut attr), 0);
@@ -352,12 +366,23 @@ pub mod guard {
352366
assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr,
353367
&mut size), 0);
354368

369+
let stackaddr = stackaddr as usize;
355370
ret = if cfg!(target_os = "freebsd") {
356-
Some(stackaddr as usize - guardsize)
371+
// FIXME does freebsd really fault *below* the guard addr?
372+
let guardaddr = stackaddr - guardsize;
373+
Some(guardaddr - PAGE_SIZE..guardaddr)
357374
} else if cfg!(target_os = "netbsd") {
358-
Some(stackaddr as usize)
375+
Some(stackaddr - guardsize..stackaddr)
376+
} else if cfg!(all(target_os = "linux", target_env = "gnu")) {
377+
// glibc used to include the guard area within the stack, as noted in the BUGS
378+
// section of `man pthread_attr_getguardsize`. This has been corrected starting
379+
// with glibc 2.27, and in some distro backports, so the guard is now placed at the
380+
// end (below) the stack. There's no easy way for us to know which we have at
381+
// runtime, so we'll just match any fault in the range right above or below the
382+
// stack base to call that fault a stack overflow.
383+
Some(stackaddr - guardsize..stackaddr + guardsize)
359384
} else {
360-
Some(stackaddr as usize + guardsize)
385+
Some(stackaddr..stackaddr + guardsize)
361386
};
362387
}
363388
assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);

Diff for: src/libstd/sys/wasm/thread.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ impl Thread {
4343
}
4444

4545
pub mod guard {
46-
pub unsafe fn current() -> Option<usize> { None }
47-
pub unsafe fn init() -> Option<usize> { None }
46+
pub type Guard = !;
47+
pub unsafe fn current() -> Option<Guard> { None }
48+
pub unsafe fn init() -> Option<Guard> { None }
4849
}

Diff for: src/libstd/sys/windows/thread.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ impl Thread {
9393

9494
#[cfg_attr(test, allow(dead_code))]
9595
pub mod guard {
96-
pub unsafe fn current() -> Option<usize> { None }
97-
pub unsafe fn init() -> Option<usize> { None }
96+
pub type Guard = !;
97+
pub unsafe fn current() -> Option<Guard> { None }
98+
pub unsafe fn init() -> Option<Guard> { None }
9899
}

Diff for: src/libstd/sys_common/thread_info.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
#![allow(dead_code)] // stack_guard isn't used right now on all platforms
1212

1313
use cell::RefCell;
14+
use sys::thread::guard::Guard;
1415
use thread::Thread;
1516

1617
struct ThreadInfo {
17-
stack_guard: Option<usize>,
18+
stack_guard: Option<Guard>,
1819
thread: Thread,
1920
}
2021

@@ -38,11 +39,11 @@ pub fn current_thread() -> Option<Thread> {
3839
ThreadInfo::with(|info| info.thread.clone())
3940
}
4041

41-
pub fn stack_guard() -> Option<usize> {
42-
ThreadInfo::with(|info| info.stack_guard).and_then(|o| o)
42+
pub fn stack_guard() -> Option<Guard> {
43+
ThreadInfo::with(|info| info.stack_guard.clone()).and_then(|o| o)
4344
}
4445

45-
pub fn set(stack_guard: Option<usize>, thread: Thread) {
46+
pub fn set(stack_guard: Option<Guard>, thread: Thread) {
4647
THREAD_INFO.with(|c| assert!(c.borrow().is_none()));
4748
THREAD_INFO.with(move |c| *c.borrow_mut() = Some(ThreadInfo{
4849
stack_guard,

0 commit comments

Comments
 (0)