1
Fork 0

Prevent UB in child process after calling libc::fork

After calling libc::fork, the child process tried to access
a TLS variable when processing a panic. This caused
a memory allocation which is UB in the child.
To prevent this from happening, the panic handler will
not access the TLS variable in case `panic::always_abort`
was called before.
This commit is contained in:
Florian Bartels 2022-09-29 11:04:25 +02:00
parent 0152393048
commit 8e70c82f57

View file

@ -308,6 +308,14 @@ pub mod panic_count {
// Additionally, the top bit of GLOBAL_PANIC_COUNT (GLOBAL_ALWAYS_ABORT_FLAG)
// records whether panic::always_abort() has been called. This can only be
// set, never cleared.
// panic::always_abort() is usually called to prevent memory allocations done by
// the panic handling in the child created by `libc::fork`.
// Memory allocations performed in a child created with `libc::fork` are undefined
// behavior in most operating systems.
// Accessing LOCAL_PANIC_COUNT in a child created by `libc::fork` would lead to a memory
// allocation. Only GLOBAL_PANIC_COUNT can be accessed in this situation. This is
// sufficient because a child process will always have exactly one thread only.
// See also #85261 for details.
//
// This could be viewed as a struct containing a single bit and an n-1-bit
// value, but if we wrote it like that it would be more than a single word,
@ -318,15 +326,26 @@ pub mod panic_count {
// panicking thread consumes at least 2 bytes of address space.
static GLOBAL_PANIC_COUNT: AtomicUsize = AtomicUsize::new(0);
// Return the state of the ALWAYS_ABORT_FLAG and number of panics.
//
// If ALWAYS_ABORT_FLAG is not set, the number is determined on a per-thread
// base (stored in LOCAL_PANIC_COUNT), i.e. it is the amount of recursive calls
// of the calling thread.
// If ALWAYS_ABORT_FLAG is set, the number equals the *global* number of panic
// calls. See above why LOCAL_PANIC_COUNT is not used.
pub fn increase() -> (bool, usize) {
(
GLOBAL_PANIC_COUNT.fetch_add(1, Ordering::Relaxed) & ALWAYS_ABORT_FLAG != 0,
let global_count = GLOBAL_PANIC_COUNT.fetch_add(1, Ordering::Relaxed);
let must_abort = global_count & ALWAYS_ABORT_FLAG != 0;
let panics = if must_abort {
global_count & !ALWAYS_ABORT_FLAG
} else {
LOCAL_PANIC_COUNT.with(|c| {
let next = c.get() + 1;
c.set(next);
next
}),
)
})
};
(must_abort, panics)
}
pub fn decrease() {