Auto merge of #102460 - flba-eb:fix_85261_prevent_alloc_after_fork, r=thomcc
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. Fixes #85261 (not only on Android systems, but also on Linux/QNX with TLS disabled, see issue for more details) Main drawbacks of this fix: * Panic messages can incorrectly omit `core::panic::PanicInfo` struct in case several panics (of multiple threads) occur at the same time. The handler cannot distinguish between multiple panics in different threads or recursive ones in the same thread, but the message will contain a hint about the uncertainty. * `panic_count::increase()` will be a bit slower as it has an additional `if`, but this should be irrelevant as it is only called in case of a panic.
This commit is contained in:
commit
50f6d337c6
2 changed files with 65 additions and 5 deletions
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue