Skip to content

Commit 28ece5a

Browse files
authored
Rollup merge of rust-lang#102721 - nbdd0121:panic, r=Amanieu
Prevent foreign Rust exceptions from being caught Fix rust-lang#102715 Use the address of a static variable (which is guaranteed to be unique per copy of std) to tell apart if a Rust exception comes from local or foreign Rust code, and abort for the latter.
2 parents eb6cd05 + bfac2da commit 28ece5a

File tree

6 files changed

+101
-20
lines changed

6 files changed

+101
-20
lines changed

library/panic_unwind/src/emcc.rs

+28-12
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ static EXCEPTION_TYPE_INFO: TypeInfo = TypeInfo {
4747
name: b"rust_panic\0".as_ptr(),
4848
};
4949

50+
// NOTE(nbdd0121): The `canary` field will be part of stable ABI after `c_unwind` stabilization.
51+
#[repr(C)]
5052
struct Exception {
53+
// See `gcc.rs` on why this is present. We already have a static here so just use it.
54+
canary: *const TypeInfo,
55+
5156
// This is necessary because C++ code can capture our exception with
5257
// std::exception_ptr and rethrow it multiple times, possibly even in
5358
// another thread.
@@ -70,27 +75,38 @@ pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
7075
let catch_data = &*(ptr as *mut CatchData);
7176

7277
let adjusted_ptr = __cxa_begin_catch(catch_data.ptr as *mut libc::c_void) as *mut Exception;
73-
let out = if catch_data.is_rust_panic {
74-
let was_caught = (*adjusted_ptr).caught.swap(true, Ordering::SeqCst);
75-
if was_caught {
76-
// Since cleanup() isn't allowed to panic, we just abort instead.
77-
intrinsics::abort();
78-
}
79-
(*adjusted_ptr).data.take().unwrap()
80-
} else {
78+
if !catch_data.is_rust_panic {
8179
super::__rust_foreign_exception();
82-
};
80+
}
81+
82+
let canary = ptr::addr_of!((*adjusted_ptr).canary).read();
83+
if !ptr::eq(canary, &EXCEPTION_TYPE_INFO) {
84+
super::__rust_foreign_exception();
85+
}
86+
87+
let was_caught = (*adjusted_ptr).caught.swap(true, Ordering::SeqCst);
88+
if was_caught {
89+
// Since cleanup() isn't allowed to panic, we just abort instead.
90+
intrinsics::abort();
91+
}
92+
let out = (*adjusted_ptr).data.take().unwrap();
8393
__cxa_end_catch();
8494
out
8595
}
8696

8797
pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
88-
let sz = mem::size_of_val(&data);
89-
let exception = __cxa_allocate_exception(sz) as *mut Exception;
98+
let exception = __cxa_allocate_exception(mem::size_of::<Exception>()) as *mut Exception;
9099
if exception.is_null() {
91100
return uw::_URC_FATAL_PHASE1_ERROR as u32;
92101
}
93-
ptr::write(exception, Exception { caught: AtomicBool::new(false), data: Some(data) });
102+
ptr::write(
103+
exception,
104+
Exception {
105+
canary: &EXCEPTION_TYPE_INFO,
106+
caught: AtomicBool::new(false),
107+
data: Some(data),
108+
},
109+
);
94110
__cxa_throw(exception as *mut _, &EXCEPTION_TYPE_INFO, exception_cleanup);
95111
}
96112

library/panic_unwind/src/gcc.rs

+27-3
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,23 @@
3838
3939
use alloc::boxed::Box;
4040
use core::any::Any;
41+
use core::ptr;
4142

4243
use unwind as uw;
4344

45+
// In case where multiple copies of std exist in a single process,
46+
// we use address of this static variable to distinguish an exception raised by
47+
// this copy and some other copy (which needs to be treated as foreign exception).
48+
static CANARY: u8 = 0;
49+
50+
// NOTE(nbdd0121)
51+
// Once `c_unwind` feature is stabilized, there will be ABI stability requirement
52+
// on this struct. The first two field must be `_Unwind_Exception` and `canary`,
53+
// as it may be accessed by a different version of the std with a different compiler.
4454
#[repr(C)]
4555
struct Exception {
4656
_uwe: uw::_Unwind_Exception,
57+
canary: *const u8,
4758
cause: Box<dyn Any + Send>,
4859
}
4960

@@ -54,6 +65,7 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
5465
exception_cleanup,
5566
private: [0; uw::unwinder_private_data_size],
5667
},
68+
canary: &CANARY,
5769
cause: data,
5870
});
5971
let exception_param = Box::into_raw(exception) as *mut uw::_Unwind_Exception;
@@ -75,10 +87,22 @@ pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
7587
if (*exception).exception_class != rust_exception_class() {
7688
uw::_Unwind_DeleteException(exception);
7789
super::__rust_foreign_exception();
78-
} else {
79-
let exception = Box::from_raw(exception as *mut Exception);
80-
exception.cause
8190
}
91+
92+
let exception = exception.cast::<Exception>();
93+
// Just access the canary field, avoid accessing the entire `Exception` as
94+
// it can be a foreign Rust exception.
95+
let canary = ptr::addr_of!((*exception).canary).read();
96+
if !ptr::eq(canary, &CANARY) {
97+
// A foreign Rust exception, treat it slightly differently from other
98+
// foreign exceptions, because call into `_Unwind_DeleteException` will
99+
// call into `__rust_drop_panic` which produces a confusing
100+
// "Rust panic must be rethrown" message.
101+
super::__rust_foreign_exception();
102+
}
103+
104+
let exception = Box::from_raw(exception as *mut Exception);
105+
exception.cause
82106
}
83107

84108
// Rust's exception class identifier. This is used by personality routines to

library/panic_unwind/src/seh.rs

+15-5
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,15 @@
4949
use alloc::boxed::Box;
5050
use core::any::Any;
5151
use core::mem::{self, ManuallyDrop};
52+
use core::ptr;
5253
use libc::{c_int, c_uint, c_void};
5354

55+
// NOTE(nbdd0121): The `canary` field will be part of stable ABI after `c_unwind` stabilization.
56+
#[repr(C)]
5457
struct Exception {
58+
// See `gcc.rs` on why this is present. We already have a static here so just use it.
59+
canary: *const _TypeDescriptor,
60+
5561
// This needs to be an Option because we catch the exception by reference
5662
// and its destructor is executed by the C++ runtime. When we take the Box
5763
// out of the exception, we need to leave the exception in a valid state
@@ -235,7 +241,7 @@ static mut TYPE_DESCRIPTOR: _TypeDescriptor = _TypeDescriptor {
235241
macro_rules! define_cleanup {
236242
($abi:tt $abi2:tt) => {
237243
unsafe extern $abi fn exception_cleanup(e: *mut Exception) {
238-
if let Exception { data: Some(b) } = e.read() {
244+
if let Exception { data: Some(b), .. } = e.read() {
239245
drop(b);
240246
super::__rust_drop_panic();
241247
}
@@ -265,7 +271,7 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
265271
// The ManuallyDrop is needed here since we don't want Exception to be
266272
// dropped when unwinding. Instead it will be dropped by exception_cleanup
267273
// which is invoked by the C++ runtime.
268-
let mut exception = ManuallyDrop::new(Exception { data: Some(data) });
274+
let mut exception = ManuallyDrop::new(Exception { canary: &TYPE_DESCRIPTOR, data: Some(data) });
269275
let throw_ptr = &mut exception as *mut _ as *mut _;
270276

271277
// This... may seems surprising, and justifiably so. On 32-bit MSVC the
@@ -321,8 +327,12 @@ pub unsafe fn cleanup(payload: *mut u8) -> Box<dyn Any + Send> {
321327
// __rust_try. This happens when a non-Rust foreign exception is caught.
322328
if payload.is_null() {
323329
super::__rust_foreign_exception();
324-
} else {
325-
let exception = &mut *(payload as *mut Exception);
326-
exception.data.take().unwrap()
327330
}
331+
let exception = payload as *mut Exception;
332+
let canary = ptr::addr_of!((*exception).canary).read();
333+
if !ptr::eq(canary, &TYPE_DESCRIPTOR) {
334+
// A foreign Rust exception.
335+
super::__rust_foreign_exception();
336+
}
337+
(*exception).data.take().unwrap()
328338
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# ignore-i686-pc-windows-gnu
2+
3+
# This test doesn't work on 32-bit MinGW as cdylib has its own copy of unwinder
4+
# so cross-DLL unwinding does not work.
5+
6+
include ../tools.mk
7+
8+
all:
9+
$(RUSTC) bar.rs --crate-type=cdylib
10+
$(RUSTC) foo.rs
11+
$(call RUN,foo) 2>&1 | $(CGREP) "Rust cannot catch foreign exceptions"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#![crate_type = "cdylib"]
2+
#![feature(c_unwind)]
3+
4+
#[no_mangle]
5+
extern "C-unwind" fn panic() {
6+
panic!();
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![feature(c_unwind)]
2+
3+
#[cfg_attr(not(windows), link(name = "bar"))]
4+
#[cfg_attr(windows, link(name = "bar.dll"))]
5+
extern "C-unwind" {
6+
fn panic();
7+
}
8+
9+
fn main() {
10+
let _ = std::panic::catch_unwind(|| {
11+
unsafe { panic() };
12+
});
13+
}

0 commit comments

Comments
 (0)