mirror of https://github.com/rust-lang/rust.git
Fix FFI-unwind unsoundness with mixed panic mode
This commit is contained in:
parent
09d52bc5d4
commit
6ef2033884
|
@ -944,6 +944,7 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> {
|
||||||
if !tcx.sess.opts.debugging_opts.thir_unsafeck {
|
if !tcx.sess.opts.debugging_opts.thir_unsafeck {
|
||||||
rustc_mir_transform::check_unsafety::check_unsafety(tcx, def_id);
|
rustc_mir_transform::check_unsafety::check_unsafety(tcx, def_id);
|
||||||
}
|
}
|
||||||
|
tcx.ensure().has_ffi_unwind_calls(def_id);
|
||||||
|
|
||||||
if tcx.hir().body_const_context(def_id).is_some() {
|
if tcx.hir().body_const_context(def_id).is_some() {
|
||||||
tcx.ensure()
|
tcx.ensure()
|
||||||
|
|
|
@ -3230,6 +3230,7 @@ declare_lint_pass! {
|
||||||
UNEXPECTED_CFGS,
|
UNEXPECTED_CFGS,
|
||||||
DEPRECATED_WHERE_CLAUSE_LOCATION,
|
DEPRECATED_WHERE_CLAUSE_LOCATION,
|
||||||
TEST_UNSTABLE_LINT,
|
TEST_UNSTABLE_LINT,
|
||||||
|
FFI_UNWIND_CALLS,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3895,3 +3896,42 @@ declare_lint! {
|
||||||
"this unstable lint is only for testing",
|
"this unstable lint is only for testing",
|
||||||
@feature_gate = sym::test_unstable_lint;
|
@feature_gate = sym::test_unstable_lint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare_lint! {
|
||||||
|
/// The `ffi_unwind_calls` lint detects calls to foreign functions or function pointers with
|
||||||
|
/// `C-unwind` or other FFI-unwind ABIs.
|
||||||
|
///
|
||||||
|
/// ### Example
|
||||||
|
///
|
||||||
|
/// ```rust,ignore (need FFI)
|
||||||
|
/// #![feature(ffi_unwind_calls)]
|
||||||
|
/// #![feature(c_unwind)]
|
||||||
|
///
|
||||||
|
/// # mod impl {
|
||||||
|
/// # #[no_mangle]
|
||||||
|
/// # pub fn "C-unwind" fn foo() {}
|
||||||
|
/// # }
|
||||||
|
///
|
||||||
|
/// extern "C-unwind" {
|
||||||
|
/// fn foo();
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn bar() {
|
||||||
|
/// unsafe { foo(); }
|
||||||
|
/// let ptr: unsafe extern "C-unwind" fn() = foo;
|
||||||
|
/// unsafe { ptr(); }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// {{produces}}
|
||||||
|
///
|
||||||
|
/// ### Explanation
|
||||||
|
///
|
||||||
|
/// For crates containing such calls, if they are compiled with `-C panic=unwind` then the
|
||||||
|
/// produced library cannot be linked with crates compiled with `-C panic=abort`. For crates
|
||||||
|
/// that desire this ability it is therefore necessary to avoid such calls.
|
||||||
|
pub FFI_UNWIND_CALLS,
|
||||||
|
Allow,
|
||||||
|
"call to foreign functions or function pointers with FFI-unwind ABI",
|
||||||
|
@feature_gate = sym::c_unwind;
|
||||||
|
}
|
||||||
|
|
|
@ -744,7 +744,7 @@ impl<'a> CrateLoader<'a> {
|
||||||
if !data.is_panic_runtime() {
|
if !data.is_panic_runtime() {
|
||||||
self.sess.err(&format!("the crate `{}` is not a panic runtime", name));
|
self.sess.err(&format!("the crate `{}` is not a panic runtime", name));
|
||||||
}
|
}
|
||||||
if data.panic_strategy() != desired_strategy {
|
if data.panic_strategy() != Some(desired_strategy) {
|
||||||
self.sess.err(&format!(
|
self.sess.err(&format!(
|
||||||
"the crate `{}` does not have the panic \
|
"the crate `{}` does not have the panic \
|
||||||
strategy `{}`",
|
strategy `{}`",
|
||||||
|
|
|
@ -60,7 +60,6 @@ use rustc_middle::ty::TyCtxt;
|
||||||
use rustc_session::config::CrateType;
|
use rustc_session::config::CrateType;
|
||||||
use rustc_session::cstore::CrateDepKind;
|
use rustc_session::cstore::CrateDepKind;
|
||||||
use rustc_session::cstore::LinkagePreference::{self, RequireDynamic, RequireStatic};
|
use rustc_session::cstore::LinkagePreference::{self, RequireDynamic, RequireStatic};
|
||||||
use rustc_target::spec::PanicStrategy;
|
|
||||||
|
|
||||||
pub(crate) fn calculate(tcx: TyCtxt<'_>) -> Dependencies {
|
pub(crate) fn calculate(tcx: TyCtxt<'_>) -> Dependencies {
|
||||||
tcx.sess
|
tcx.sess
|
||||||
|
@ -367,7 +366,7 @@ fn verify_ok(tcx: TyCtxt<'_>, list: &[Linkage]) {
|
||||||
prev_name, cur_name
|
prev_name, cur_name
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
panic_runtime = Some((cnum, tcx.panic_strategy(cnum)));
|
panic_runtime = Some((cnum, tcx.panic_strategy(cnum).unwrap()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,18 +396,14 @@ fn verify_ok(tcx: TyCtxt<'_>, list: &[Linkage]) {
|
||||||
if let Linkage::NotLinked = *linkage {
|
if let Linkage::NotLinked = *linkage {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if desired_strategy == PanicStrategy::Abort {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let cnum = CrateNum::new(i + 1);
|
let cnum = CrateNum::new(i + 1);
|
||||||
if tcx.is_compiler_builtins(cnum) {
|
if tcx.is_compiler_builtins(cnum) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let found_strategy = tcx.panic_strategy(cnum);
|
if let Some(found_strategy) = tcx.panic_strategy(cnum) && desired_strategy != found_strategy {
|
||||||
if desired_strategy != found_strategy {
|
|
||||||
sess.err(&format!(
|
sess.err(&format!(
|
||||||
"the crate `{}` is compiled with the \
|
"the crate `{}` requires \
|
||||||
panic strategy `{}` which is \
|
panic strategy `{}` which is \
|
||||||
incompatible with this crate's \
|
incompatible with this crate's \
|
||||||
strategy of `{}`",
|
strategy of `{}`",
|
||||||
|
|
|
@ -1759,7 +1759,7 @@ impl CrateMetadata {
|
||||||
self.dep_kind.with_lock(|dep_kind| *dep_kind = f(*dep_kind))
|
self.dep_kind.with_lock(|dep_kind| *dep_kind = f(*dep_kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn panic_strategy(&self) -> PanicStrategy {
|
pub(crate) fn panic_strategy(&self) -> Option<PanicStrategy> {
|
||||||
self.root.panic_strategy
|
self.root.panic_strategy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -665,7 +665,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
|
||||||
triple: tcx.sess.opts.target_triple.clone(),
|
triple: tcx.sess.opts.target_triple.clone(),
|
||||||
hash: tcx.crate_hash(LOCAL_CRATE),
|
hash: tcx.crate_hash(LOCAL_CRATE),
|
||||||
stable_crate_id: tcx.def_path_hash(LOCAL_CRATE.as_def_id()).stable_crate_id(),
|
stable_crate_id: tcx.def_path_hash(LOCAL_CRATE.as_def_id()).stable_crate_id(),
|
||||||
panic_strategy: tcx.sess.panic_strategy(),
|
panic_strategy: tcx.required_panic_strategy(()),
|
||||||
panic_in_drop_strategy: tcx.sess.opts.debugging_opts.panic_in_drop,
|
panic_in_drop_strategy: tcx.sess.opts.debugging_opts.panic_in_drop,
|
||||||
edition: tcx.sess.edition(),
|
edition: tcx.sess.edition(),
|
||||||
has_global_allocator: tcx.has_global_allocator(LOCAL_CRATE),
|
has_global_allocator: tcx.has_global_allocator(LOCAL_CRATE),
|
||||||
|
|
|
@ -217,7 +217,7 @@ pub(crate) struct CrateRoot {
|
||||||
extra_filename: String,
|
extra_filename: String,
|
||||||
hash: Svh,
|
hash: Svh,
|
||||||
stable_crate_id: StableCrateId,
|
stable_crate_id: StableCrateId,
|
||||||
panic_strategy: PanicStrategy,
|
panic_strategy: Option<PanicStrategy>,
|
||||||
panic_in_drop_strategy: PanicStrategy,
|
panic_in_drop_strategy: PanicStrategy,
|
||||||
edition: Edition,
|
edition: Edition,
|
||||||
has_global_allocator: bool,
|
has_global_allocator: bool,
|
||||||
|
|
|
@ -1365,9 +1365,16 @@ rustc_queries! {
|
||||||
desc { "query a crate is `#![profiler_runtime]`" }
|
desc { "query a crate is `#![profiler_runtime]`" }
|
||||||
separate_provide_extern
|
separate_provide_extern
|
||||||
}
|
}
|
||||||
query panic_strategy(_: CrateNum) -> PanicStrategy {
|
query has_ffi_unwind_calls(key: LocalDefId) -> bool {
|
||||||
|
desc { |tcx| "check if `{}` contains FFI-unwind calls", tcx.def_path_str(key.to_def_id()) }
|
||||||
|
cache_on_disk_if { true }
|
||||||
|
}
|
||||||
|
query required_panic_strategy(_: ()) -> Option<PanicStrategy> {
|
||||||
|
desc { "compute the required panic strategy for the current crate" }
|
||||||
|
}
|
||||||
|
query panic_strategy(_: CrateNum) -> Option<PanicStrategy> {
|
||||||
fatal_cycle
|
fatal_cycle
|
||||||
desc { "query a crate's configured panic strategy" }
|
desc { "query a crate's required panic strategy" }
|
||||||
separate_provide_extern
|
separate_provide_extern
|
||||||
}
|
}
|
||||||
query panic_in_drop_strategy(_: CrateNum) -> PanicStrategy {
|
query panic_in_drop_strategy(_: CrateNum) -> PanicStrategy {
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
use rustc_hir::def::DefKind;
|
||||||
|
use rustc_hir::def_id::LocalDefId;
|
||||||
|
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);
|
||||||
|
let is_function = match kind {
|
||||||
|
DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true,
|
||||||
|
_ => tcx.is_closure(def_id),
|
||||||
|
};
|
||||||
|
if !is_function {
|
||||||
|
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<'_>, (): ()) -> Option<PanicStrategy> {
|
||||||
|
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) {
|
||||||
|
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 };
|
||||||
|
}
|
|
@ -57,6 +57,7 @@ mod dest_prop;
|
||||||
pub mod dump_mir;
|
pub mod dump_mir;
|
||||||
mod early_otherwise_branch;
|
mod early_otherwise_branch;
|
||||||
mod elaborate_drops;
|
mod elaborate_drops;
|
||||||
|
mod ffi_unwind_calls;
|
||||||
mod function_item_references;
|
mod function_item_references;
|
||||||
mod generator;
|
mod generator;
|
||||||
mod inline;
|
mod inline;
|
||||||
|
@ -96,6 +97,7 @@ pub fn provide(providers: &mut Providers) {
|
||||||
check_unsafety::provide(providers);
|
check_unsafety::provide(providers);
|
||||||
check_packed_ref::provide(providers);
|
check_packed_ref::provide(providers);
|
||||||
coverage::query::provide(providers);
|
coverage::query::provide(providers);
|
||||||
|
ffi_unwind_calls::provide(providers);
|
||||||
shim::provide(providers);
|
shim::provide(providers);
|
||||||
*providers = Providers {
|
*providers = Providers {
|
||||||
mir_keys,
|
mir_keys,
|
||||||
|
@ -221,6 +223,9 @@ fn mir_const<'tcx>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// has_ffi_unwind_calls query uses the raw mir, so make sure it is run.
|
||||||
|
tcx.ensure().has_ffi_unwind_calls(def.did);
|
||||||
|
|
||||||
let mut body = tcx.mir_built(def).steal();
|
let mut body = tcx.mir_built(def).steal();
|
||||||
|
|
||||||
rustc_middle::mir::dump_mir(tcx, None, "mir_map", &0, &body, |_, _| Ok(()));
|
rustc_middle::mir::dump_mir(tcx, None, "mir_map", &0, &body, |_, _| Ok(()));
|
||||||
|
|
|
@ -210,6 +210,8 @@
|
||||||
#![allow(unused_lifetimes)]
|
#![allow(unused_lifetimes)]
|
||||||
// Tell the compiler to link to either panic_abort or panic_unwind
|
// Tell the compiler to link to either panic_abort or panic_unwind
|
||||||
#![needs_panic_runtime]
|
#![needs_panic_runtime]
|
||||||
|
// Ensure that std can be linked against panic_abort despite compiled with `-C panic=unwind`
|
||||||
|
#![cfg_attr(not(bootstrap), deny(ffi_unwind_calls))]
|
||||||
// std may use features in a platform-specific way
|
// std may use features in a platform-specific way
|
||||||
#![allow(unused_features)]
|
#![allow(unused_features)]
|
||||||
#![cfg_attr(test, feature(internal_output_capture, print_internals, update_panic_count))]
|
#![cfg_attr(test, feature(internal_output_capture, print_internals, update_panic_count))]
|
||||||
|
|
Loading…
Reference in New Issue