forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rollup merge of rust-lang#97235 - nbdd0121:unwind, r=Amanieu
Fix FFI-unwind unsoundness with mixed panic mode UB maybe introduced when an FFI exception happens in a `C-unwind` foreign function and it propagates through a crate compiled with `-C panic=unwind` into a crate compiled with `-C panic=abort` (rust-lang#96926). To prevent this unsoundness from happening, we will disallow a crate compiled with `-C panic=unwind` to be linked into `panic-abort` *if* it contains a call to `C-unwind` foreign function or function pointer. If no such call exists, then we continue to allow such mixed panic mode linking because it's sound (and stable). In fact we still need the ability to do mixed panic mode linking for std, because we only compile std once with `-C panic=unwind` and link it regardless panic strategy. For libraries that wish to remain compile-once-and-linkable-to-both-panic-runtimes, a `ffi_unwind_calls` lint is added (gated under `c_unwind` feature gate) to flag any FFI unwind calls that will cause the linkable panic runtime be restricted. In summary: ```rust #![warn(ffi_unwind_calls)] mod foo { #[no_mangle] pub extern "C-unwind" fn foo() {} } extern "C-unwind" { fn foo(); } fn main() { // Call to Rust function is fine regardless ABI. foo::foo(); // Call to foreign function, will cause the crate to be unlinkable to panic-abort if compiled with `-Cpanic=unwind`. unsafe { foo(); } //~^ WARNING call to foreign function with FFI-unwind ABI let ptr: extern "C-unwind" fn() = foo::foo; // Call to function pointer, will cause the crate to be unlinkable to panic-abort if compiled with `-Cpanic=unwind`. ptr(); //~^ WARNING call to function pointer with FFI-unwind ABI } ``` Fix rust-lang#96926 ``@rustbot`` label: T-compiler F-c_unwind
- Loading branch information
Showing
27 changed files
with
364 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
use rustc_hir::def_id::{CrateNum, LocalDefId, LOCAL_CRATE}; | ||
use rustc_middle::mir::*; | ||
use rustc_middle::ty::layout; | ||
use rustc_middle::ty::query::Providers; | ||
use rustc_middle::ty::{self, TyCtxt}; | ||
use rustc_session::lint::builtin::FFI_UNWIND_CALLS; | ||
use rustc_target::spec::abi::Abi; | ||
use rustc_target::spec::PanicStrategy; | ||
|
||
fn abi_can_unwind(abi: Abi) -> bool { | ||
use Abi::*; | ||
match abi { | ||
C { unwind } | ||
| System { unwind } | ||
| Cdecl { unwind } | ||
| Stdcall { unwind } | ||
| Fastcall { unwind } | ||
| Vectorcall { unwind } | ||
| Thiscall { unwind } | ||
| Aapcs { unwind } | ||
| Win64 { unwind } | ||
| SysV64 { unwind } => unwind, | ||
PtxKernel | ||
| Msp430Interrupt | ||
| X86Interrupt | ||
| AmdGpuKernel | ||
| EfiApi | ||
| AvrInterrupt | ||
| AvrNonBlockingInterrupt | ||
| CCmseNonSecureCall | ||
| Wasm | ||
| RustIntrinsic | ||
| PlatformIntrinsic | ||
| Unadjusted => false, | ||
Rust | RustCall | RustCold => true, | ||
} | ||
} | ||
|
||
// Check if the body of this def_id can possibly leak a foreign unwind into Rust code. | ||
fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool { | ||
debug!("has_ffi_unwind_calls({local_def_id:?})"); | ||
|
||
// Only perform check on functions because constants cannot call FFI functions. | ||
let def_id = local_def_id.to_def_id(); | ||
let kind = tcx.def_kind(def_id); | ||
if !kind.is_fn_like() { | ||
return false; | ||
} | ||
|
||
let body = &*tcx.mir_built(ty::WithOptConstParam::unknown(local_def_id)).borrow(); | ||
|
||
let body_ty = tcx.type_of(def_id); | ||
let body_abi = match body_ty.kind() { | ||
ty::FnDef(..) => body_ty.fn_sig(tcx).abi(), | ||
ty::Closure(..) => Abi::RustCall, | ||
ty::Generator(..) => Abi::Rust, | ||
_ => span_bug!(body.span, "unexpected body ty: {:?}", body_ty), | ||
}; | ||
let body_can_unwind = layout::fn_can_unwind(tcx, Some(def_id), body_abi); | ||
|
||
// Foreign unwinds cannot leak past functions that themselves cannot unwind. | ||
if !body_can_unwind { | ||
return false; | ||
} | ||
|
||
let mut tainted = false; | ||
|
||
for block in body.basic_blocks() { | ||
if block.is_cleanup { | ||
continue; | ||
} | ||
let Some(terminator) = &block.terminator else { continue }; | ||
let TerminatorKind::Call { func, .. } = &terminator.kind else { continue }; | ||
|
||
let ty = func.ty(body, tcx); | ||
let sig = ty.fn_sig(tcx); | ||
|
||
// Rust calls cannot themselves create foreign unwinds. | ||
if let Abi::Rust | Abi::RustCall | Abi::RustCold = sig.abi() { | ||
continue; | ||
}; | ||
|
||
let fn_def_id = match ty.kind() { | ||
ty::FnPtr(_) => None, | ||
&ty::FnDef(def_id, _) => { | ||
// Rust calls cannot themselves create foreign unwinds. | ||
if !tcx.is_foreign_item(def_id) { | ||
continue; | ||
} | ||
Some(def_id) | ||
} | ||
_ => bug!("invalid callee of type {:?}", ty), | ||
}; | ||
|
||
if layout::fn_can_unwind(tcx, fn_def_id, sig.abi()) && abi_can_unwind(sig.abi()) { | ||
// We have detected a call that can possibly leak foreign unwind. | ||
// | ||
// Because the function body itself can unwind, we are not aborting this function call | ||
// upon unwind, so this call can possibly leak foreign unwind into Rust code if the | ||
// panic runtime linked is panic-abort. | ||
|
||
let lint_root = body.source_scopes[terminator.source_info.scope] | ||
.local_data | ||
.as_ref() | ||
.assert_crate_local() | ||
.lint_root; | ||
let span = terminator.source_info.span; | ||
|
||
tcx.struct_span_lint_hir(FFI_UNWIND_CALLS, lint_root, span, |lint| { | ||
let msg = match fn_def_id { | ||
Some(_) => "call to foreign function with FFI-unwind ABI", | ||
None => "call to function pointer with FFI-unwind ABI", | ||
}; | ||
let mut db = lint.build(msg); | ||
db.span_label(span, msg); | ||
db.emit(); | ||
}); | ||
|
||
tainted = true; | ||
} | ||
} | ||
|
||
tainted | ||
} | ||
|
||
fn required_panic_strategy(tcx: TyCtxt<'_>, cnum: CrateNum) -> Option<PanicStrategy> { | ||
assert_eq!(cnum, LOCAL_CRATE); | ||
|
||
if tcx.is_panic_runtime(LOCAL_CRATE) { | ||
return Some(tcx.sess.panic_strategy()); | ||
} | ||
|
||
if tcx.sess.panic_strategy() == PanicStrategy::Abort { | ||
return Some(PanicStrategy::Abort); | ||
} | ||
|
||
for def_id in tcx.hir().body_owners() { | ||
if tcx.has_ffi_unwind_calls(def_id) { | ||
// Given that this crate is compiled in `-C panic=unwind`, the `AbortUnwindingCalls` | ||
// MIR pass will not be run on FFI-unwind call sites, therefore a foreign exception | ||
// can enter Rust through these sites. | ||
// | ||
// On the other hand, crates compiled with `-C panic=abort` expects that all Rust | ||
// functions cannot unwind (whether it's caused by Rust panic or foreign exception), | ||
// and this expectation mismatch can cause unsoundness (#96926). | ||
// | ||
// To address this issue, we enforce that if FFI-unwind calls are used in a crate | ||
// compiled with `panic=unwind`, then the final panic strategy must be `panic=unwind`. | ||
// This will ensure that no crates will have wrong unwindability assumption. | ||
// | ||
// It should be noted that it is okay to link `panic=unwind` into a `panic=abort` | ||
// program if it contains no FFI-unwind calls. In such case foreign exception can only | ||
// enter Rust in a `panic=abort` crate, which will lead to an abort. There will also | ||
// be no exceptions generated from Rust, so the assumption which `panic=abort` crates | ||
// make, that no Rust function can unwind, indeed holds for crates compiled with | ||
// `panic=unwind` as well. In such case this function returns `None`, indicating that | ||
// the crate does not require a particular final panic strategy, and can be freely | ||
// linked to crates with either strategy (we need such ability for libstd and its | ||
// dependencies). | ||
return Some(PanicStrategy::Unwind); | ||
} | ||
} | ||
|
||
// This crate can be linked with either runtime. | ||
None | ||
} | ||
|
||
pub(crate) fn provide(providers: &mut Providers) { | ||
*providers = Providers { has_ffi_unwind_calls, required_panic_strategy, ..*providers }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.