Skip to content

Commit 41aaa90

Browse files
committed
Auto merge of #70212 - Amanieu:catch_foreign, r=Mark-Simulacrum
Abort when foreign exceptions are caught by catch_unwind Prior to this PR, foreign exceptions were not caught by catch_unwind, and instead passed through invisibly. This represented a painful soundness hole in some libraries ([take_mut](https://github.com/Sgeo/take_mut/blob/master/src/lib.rs#L37)), which relied on `catch_unwind` to handle all possible exit paths from a closure. With this PR, foreign exceptions are now caught by `catch_unwind` and will trigger an abort since catching foreign exceptions is currently UB according to the latest proposals by the FFI unwind project group. cc @rust-lang/wg-ffi-unwind
2 parents 2aa741a + 239f833 commit 41aaa90

File tree

26 files changed

+275
-105
lines changed

26 files changed

+275
-105
lines changed

library/panic_abort/src/lib.rs

+11
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ pub mod personalities {
117117
1 // `ExceptionContinueSearch`
118118
}
119119

120+
// Similar to above, this corresponds to the `eh_catch_typeinfo` lang item
121+
// that's only used on Emscripten currently.
122+
//
123+
// Since panics don't generate exceptions and foreign exceptions are
124+
// currently UB with -C panic=abort (although this may be subject to
125+
// change), any catch_unwind calls will never use this typeinfo.
126+
#[rustc_std_internal_symbol]
127+
#[allow(non_upper_case_globals)]
128+
#[cfg(target_os = "emscripten")]
129+
static rust_eh_catch_typeinfo: [usize; 2] = [0; 2];
130+
120131
// These two are called by our startup objects on i686-pc-windows-gnu, but
121132
// they don't need to do anything so the bodies are nops.
122133
#[rustc_std_internal_symbol]

library/panic_unwind/src/dwarf/eh.rs

+4-12
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,7 @@ pub enum EHAction {
5151

5252
pub const USING_SJLJ_EXCEPTIONS: bool = cfg!(all(target_os = "ios", target_arch = "arm"));
5353

54-
pub unsafe fn find_eh_action(
55-
lsda: *const u8,
56-
context: &EHContext<'_>,
57-
foreign_exception: bool,
58-
) -> Result<EHAction, ()> {
54+
pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>) -> Result<EHAction, ()> {
5955
if lsda.is_null() {
6056
return Ok(EHAction::None);
6157
}
@@ -98,7 +94,7 @@ pub unsafe fn find_eh_action(
9894
return Ok(EHAction::None);
9995
} else {
10096
let lpad = lpad_base + cs_lpad;
101-
return Ok(interpret_cs_action(cs_action, lpad, foreign_exception));
97+
return Ok(interpret_cs_action(cs_action, lpad));
10298
}
10399
}
104100
}
@@ -123,21 +119,17 @@ pub unsafe fn find_eh_action(
123119
// Can never have null landing pad for sjlj -- that would have
124120
// been indicated by a -1 call site index.
125121
let lpad = (cs_lpad + 1) as usize;
126-
return Ok(interpret_cs_action(cs_action, lpad, foreign_exception));
122+
return Ok(interpret_cs_action(cs_action, lpad));
127123
}
128124
}
129125
}
130126
}
131127

132-
fn interpret_cs_action(cs_action: u64, lpad: usize, foreign_exception: bool) -> EHAction {
128+
fn interpret_cs_action(cs_action: u64, lpad: usize) -> EHAction {
133129
if cs_action == 0 {
134130
// If cs_action is 0 then this is a cleanup (Drop::drop). We run these
135131
// for both Rust panics and foreign exceptions.
136132
EHAction::Cleanup(lpad)
137-
} else if foreign_exception {
138-
// catch_unwind should not catch foreign exceptions, only Rust panics.
139-
// Instead just continue unwinding.
140-
EHAction::None
141133
} else {
142134
// Stop unwinding Rust panics at catch_unwind.
143135
EHAction::Catch(lpad)

library/panic_unwind/src/emcc.rs

+30-16
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
99
use alloc::boxed::Box;
1010
use core::any::Any;
11+
use core::intrinsics;
1112
use core::mem;
1213
use core::ptr;
14+
use core::sync::atomic::{AtomicBool, Ordering};
1315
use libc::{self, c_int};
1416
use unwind as uw;
1517

@@ -47,6 +49,11 @@ static EXCEPTION_TYPE_INFO: TypeInfo = TypeInfo {
4749
};
4850

4951
struct Exception {
52+
// This is necessary because C++ code can capture our execption with
53+
// std::exception_ptr and rethrow it multiple times, possibly even in
54+
// another thread.
55+
caught: AtomicBool,
56+
5057
// This needs to be an Option because the object's lifetime follows C++
5158
// semantics: when catch_unwind moves the Box out of the exception it must
5259
// still leave the exception object in a valid state because its destructor
@@ -55,11 +62,27 @@ struct Exception {
5562
}
5663

5764
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
58-
assert!(!ptr.is_null());
59-
let adjusted_ptr = __cxa_begin_catch(ptr as *mut libc::c_void) as *mut Exception;
60-
let ex = (*adjusted_ptr).data.take();
65+
// intrinsics::try actually gives us a pointer to this structure.
66+
#[repr(C)]
67+
struct CatchData {
68+
ptr: *mut u8,
69+
is_rust_panic: bool,
70+
}
71+
let catch_data = &*(ptr as *mut CatchData);
72+
73+
let adjusted_ptr = __cxa_begin_catch(catch_data.ptr as *mut libc::c_void) as *mut Exception;
74+
let out = if catch_data.is_rust_panic {
75+
let was_caught = (*adjusted_ptr).caught.swap(true, Ordering::SeqCst);
76+
if was_caught {
77+
// Since cleanup() isn't allowed to panic, we just abort instead.
78+
intrinsics::abort();
79+
}
80+
(*adjusted_ptr).data.take().unwrap()
81+
} else {
82+
super::__rust_foreign_exception();
83+
};
6184
__cxa_end_catch();
62-
ex.unwrap()
85+
out
6386
}
6487

6588
pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
@@ -68,25 +91,16 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
6891
if exception.is_null() {
6992
return uw::_URC_FATAL_PHASE1_ERROR as u32;
7093
}
71-
ptr::write(exception, Exception { data: Some(data) });
94+
ptr::write(exception, Exception { caught: AtomicBool::new(false), data: Some(data) });
7295
__cxa_throw(exception as *mut _, &EXCEPTION_TYPE_INFO, exception_cleanup);
7396
}
7497

75-
// On WASM and ARM, the destructor returns the pointer to the object.
76-
cfg_if::cfg_if! {
77-
if #[cfg(any(target_arch = "arm", target_arch = "wasm32"))] {
78-
type DestructorRet = *mut libc::c_void;
79-
} else {
80-
type DestructorRet = ();
81-
}
82-
}
83-
extern "C" fn exception_cleanup(ptr: *mut libc::c_void) -> DestructorRet {
98+
extern "C" fn exception_cleanup(ptr: *mut libc::c_void) -> *mut libc::c_void {
8499
unsafe {
85100
if let Some(b) = (ptr as *mut Exception).read().data {
86101
drop(b);
87102
super::__rust_drop_panic();
88103
}
89-
#[cfg(any(target_arch = "arm", target_arch = "wasm32"))]
90104
ptr
91105
}
92106
}
@@ -109,7 +123,7 @@ extern "C" {
109123
fn __cxa_throw(
110124
thrown_exception: *mut libc::c_void,
111125
tinfo: *const TypeInfo,
112-
dest: extern "C" fn(*mut libc::c_void) -> DestructorRet,
126+
dest: extern "C" fn(*mut libc::c_void) -> *mut libc::c_void,
113127
) -> !;
114128
fn __gxx_personality_v0(
115129
version: c_int,

library/panic_unwind/src/gcc.rs

+13-13
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,14 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
7373
}
7474

7575
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
76-
let exception = Box::from_raw(ptr as *mut Exception);
77-
exception.cause
76+
let exception = ptr as *mut uw::_Unwind_Exception;
77+
if (*exception).exception_class != rust_exception_class() {
78+
uw::_Unwind_DeleteException(exception);
79+
super::__rust_foreign_exception();
80+
} else {
81+
let exception = Box::from_raw(exception as *mut Exception);
82+
exception.cause
83+
}
7884
}
7985

8086
// Rust's exception class identifier. This is used by personality routines to
@@ -164,9 +170,7 @@ cfg_if::cfg_if! {
164170
// _Unwind_Context in our libunwind bindings and fetch the required data from there
165171
// directly, bypassing DWARF compatibility functions.
166172

167-
let exception_class = (*exception_object).exception_class;
168-
let foreign_exception = exception_class != rust_exception_class();
169-
let eh_action = match find_eh_action(context, foreign_exception) {
173+
let eh_action = match find_eh_action(context) {
170174
Ok(action) => action,
171175
Err(_) => return uw::_URC_FAILURE,
172176
};
@@ -221,15 +225,14 @@ cfg_if::cfg_if! {
221225
// and indirectly on Windows x86_64 via SEH.
222226
unsafe extern "C" fn rust_eh_personality_impl(version: c_int,
223227
actions: uw::_Unwind_Action,
224-
exception_class: uw::_Unwind_Exception_Class,
228+
_exception_class: uw::_Unwind_Exception_Class,
225229
exception_object: *mut uw::_Unwind_Exception,
226230
context: *mut uw::_Unwind_Context)
227231
-> uw::_Unwind_Reason_Code {
228232
if version != 1 {
229233
return uw::_URC_FATAL_PHASE1_ERROR;
230234
}
231-
let foreign_exception = exception_class != rust_exception_class();
232-
let eh_action = match find_eh_action(context, foreign_exception) {
235+
let eh_action = match find_eh_action(context) {
233236
Ok(action) => action,
234237
Err(_) => return uw::_URC_FATAL_PHASE1_ERROR,
235238
};
@@ -293,10 +296,7 @@ cfg_if::cfg_if! {
293296
}
294297
}
295298

296-
unsafe fn find_eh_action(
297-
context: *mut uw::_Unwind_Context,
298-
foreign_exception: bool,
299-
) -> Result<EHAction, ()> {
299+
unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) -> Result<EHAction, ()> {
300300
let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8;
301301
let mut ip_before_instr: c_int = 0;
302302
let ip = uw::_Unwind_GetIPInfo(context, &mut ip_before_instr);
@@ -308,7 +308,7 @@ unsafe fn find_eh_action(
308308
get_text_start: &|| uw::_Unwind_GetTextRelBase(context),
309309
get_data_start: &|| uw::_Unwind_GetDataRelBase(context),
310310
};
311-
eh::find_eh_action(lsda, &eh_context, foreign_exception)
311+
eh::find_eh_action(lsda, &eh_context)
312312
}
313313

314314
// Frame unwind info registration

library/panic_unwind/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ extern "C" {
8888
/// Handler in libstd called when a panic object is dropped outside of
8989
/// `catch_unwind`.
9090
fn __rust_drop_panic() -> !;
91+
92+
/// Handler in libstd called when a foreign exception is caught.
93+
fn __rust_foreign_exception() -> !;
9194
}
9295

9396
mod dwarf;

library/panic_unwind/src/seh.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -309,15 +309,21 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
309309

310310
extern "system" {
311311
#[unwind(allowed)]
312-
pub fn _CxxThrowException(pExceptionObject: *mut c_void, pThrowInfo: *mut u8) -> !;
312+
fn _CxxThrowException(pExceptionObject: *mut c_void, pThrowInfo: *mut u8) -> !;
313313
}
314314

315315
_CxxThrowException(throw_ptr, &mut THROW_INFO as *mut _ as *mut _);
316316
}
317317

318318
pub unsafe fn cleanup(payload: *mut u8) -> Box<dyn Any + Send> {
319-
let exception = &mut *(payload as *mut Exception);
320-
exception.data.take().unwrap()
319+
// A NULL payload here means that we got here from the catch (...) of
320+
// __rust_try. This happens when a non-Rust foreign exception is caught.
321+
if payload.is_null() {
322+
super::__rust_foreign_exception();
323+
} else {
324+
let exception = &mut *(payload as *mut Exception);
325+
exception.data.take().unwrap()
326+
}
321327
}
322328

323329
// This is required by the compiler to exist (e.g., it's a lang item), but

library/std/src/panic.rs

+3
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,9 @@ impl<F: Future> Future for AssertUnwindSafe<F> {
359359
/// aborting the process as well. This function *only* catches unwinding panics,
360360
/// not those that abort the process.
361361
///
362+
/// Also note that unwinding into Rust code with a foreign exception (e.g. a
363+
/// an exception thrown from C++ code) is undefined behavior.
364+
///
362365
/// # Examples
363366
///
364367
/// ```

library/std/src/panicking.rs

+8
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ extern "C" fn __rust_drop_panic() -> ! {
6060
rtabort!("Rust panics must be rethrown");
6161
}
6262

63+
/// This function is called by the panic runtime if it catches an exception
64+
/// object which does not correspond to a Rust panic.
65+
#[cfg(not(test))]
66+
#[rustc_std_internal_symbol]
67+
extern "C" fn __rust_foreign_exception() -> ! {
68+
rtabort!("Rust cannot catch foreign exceptions");
69+
}
70+
6371
#[derive(Copy, Clone)]
6472
enum Hook {
6573
Default,

src/librustc_codegen_llvm/context.rs

+21
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ pub struct CodegenCx<'ll, 'tcx> {
8282
pub dbg_cx: Option<debuginfo::CrateDebugContext<'ll, 'tcx>>,
8383

8484
eh_personality: Cell<Option<&'ll Value>>,
85+
eh_catch_typeinfo: Cell<Option<&'ll Value>>,
8586
pub rust_try_fn: Cell<Option<&'ll Value>>,
8687

8788
intrinsics: RefCell<FxHashMap<&'static str, &'ll Value>>,
@@ -311,6 +312,7 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
311312
coverage_cx,
312313
dbg_cx,
313314
eh_personality: Cell::new(None),
315+
eh_catch_typeinfo: Cell::new(None),
314316
rust_try_fn: Cell::new(None),
315317
intrinsics: Default::default(),
316318
local_gen_sym_counter: Cell::new(0),
@@ -819,6 +821,25 @@ impl CodegenCx<'b, 'tcx> {
819821
}
820822
None
821823
}
824+
825+
crate fn eh_catch_typeinfo(&self) -> &'b Value {
826+
if let Some(eh_catch_typeinfo) = self.eh_catch_typeinfo.get() {
827+
return eh_catch_typeinfo;
828+
}
829+
let tcx = self.tcx;
830+
assert!(self.sess().target.target.options.is_like_emscripten);
831+
let eh_catch_typeinfo = match tcx.lang_items().eh_catch_typeinfo() {
832+
Some(def_id) => self.get_static(def_id),
833+
_ => {
834+
let ty = self
835+
.type_struct(&[self.type_ptr_to(self.type_isize()), self.type_i8p()], false);
836+
self.declare_global("rust_eh_catch_typeinfo", ty)
837+
}
838+
};
839+
let eh_catch_typeinfo = self.const_bitcast(eh_catch_typeinfo, self.type_i8p());
840+
self.eh_catch_typeinfo.set(Some(eh_catch_typeinfo));
841+
eh_catch_typeinfo
842+
}
822843
}
823844

824845
impl<'b, 'tcx> CodegenCx<'b, 'tcx> {

0 commit comments

Comments
 (0)