|
| 1 | +use rustc_hir::def_id::{CrateNum, LocalDefId, LOCAL_CRATE}; |
| 2 | +use rustc_middle::mir::*; |
| 3 | +use rustc_middle::ty::layout; |
| 4 | +use rustc_middle::ty::query::Providers; |
| 5 | +use rustc_middle::ty::{self, TyCtxt}; |
| 6 | +use rustc_session::lint::builtin::FFI_UNWIND_CALLS; |
| 7 | +use rustc_target::spec::abi::Abi; |
| 8 | +use rustc_target::spec::PanicStrategy; |
| 9 | + |
| 10 | +fn abi_can_unwind(abi: Abi) -> bool { |
| 11 | + use Abi::*; |
| 12 | + match abi { |
| 13 | + C { unwind } |
| 14 | + | System { unwind } |
| 15 | + | Cdecl { unwind } |
| 16 | + | Stdcall { unwind } |
| 17 | + | Fastcall { unwind } |
| 18 | + | Vectorcall { unwind } |
| 19 | + | Thiscall { unwind } |
| 20 | + | Aapcs { unwind } |
| 21 | + | Win64 { unwind } |
| 22 | + | SysV64 { unwind } => unwind, |
| 23 | + PtxKernel |
| 24 | + | Msp430Interrupt |
| 25 | + | X86Interrupt |
| 26 | + | AmdGpuKernel |
| 27 | + | EfiApi |
| 28 | + | AvrInterrupt |
| 29 | + | AvrNonBlockingInterrupt |
| 30 | + | CCmseNonSecureCall |
| 31 | + | Wasm |
| 32 | + | RustIntrinsic |
| 33 | + | PlatformIntrinsic |
| 34 | + | Unadjusted => false, |
| 35 | + Rust | RustCall | RustCold => true, |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +// Check if the body of this def_id can possibly leak a foreign unwind into Rust code. |
| 40 | +fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool { |
| 41 | + debug!("has_ffi_unwind_calls({local_def_id:?})"); |
| 42 | + |
| 43 | + // Only perform check on functions because constants cannot call FFI functions. |
| 44 | + let def_id = local_def_id.to_def_id(); |
| 45 | + let kind = tcx.def_kind(def_id); |
| 46 | + if !kind.is_fn_like() { |
| 47 | + return false; |
| 48 | + } |
| 49 | + |
| 50 | + let body = &*tcx.mir_built(ty::WithOptConstParam::unknown(local_def_id)).borrow(); |
| 51 | + |
| 52 | + let body_ty = tcx.type_of(def_id); |
| 53 | + let body_abi = match body_ty.kind() { |
| 54 | + ty::FnDef(..) => body_ty.fn_sig(tcx).abi(), |
| 55 | + ty::Closure(..) => Abi::RustCall, |
| 56 | + ty::Generator(..) => Abi::Rust, |
| 57 | + _ => span_bug!(body.span, "unexpected body ty: {:?}", body_ty), |
| 58 | + }; |
| 59 | + let body_can_unwind = layout::fn_can_unwind(tcx, Some(def_id), body_abi); |
| 60 | + |
| 61 | + // Foreign unwinds cannot leak past functions that themselves cannot unwind. |
| 62 | + if !body_can_unwind { |
| 63 | + return false; |
| 64 | + } |
| 65 | + |
| 66 | + let mut tainted = false; |
| 67 | + |
| 68 | + for block in body.basic_blocks() { |
| 69 | + if block.is_cleanup { |
| 70 | + continue; |
| 71 | + } |
| 72 | + let Some(terminator) = &block.terminator else { continue }; |
| 73 | + let TerminatorKind::Call { func, .. } = &terminator.kind else { continue }; |
| 74 | + |
| 75 | + let ty = func.ty(body, tcx); |
| 76 | + let sig = ty.fn_sig(tcx); |
| 77 | + |
| 78 | + // Rust calls cannot themselves create foreign unwinds. |
| 79 | + if let Abi::Rust | Abi::RustCall | Abi::RustCold = sig.abi() { |
| 80 | + continue; |
| 81 | + }; |
| 82 | + |
| 83 | + let fn_def_id = match ty.kind() { |
| 84 | + ty::FnPtr(_) => None, |
| 85 | + &ty::FnDef(def_id, _) => { |
| 86 | + // Rust calls cannot themselves create foreign unwinds. |
| 87 | + if !tcx.is_foreign_item(def_id) { |
| 88 | + continue; |
| 89 | + } |
| 90 | + Some(def_id) |
| 91 | + } |
| 92 | + _ => bug!("invalid callee of type {:?}", ty), |
| 93 | + }; |
| 94 | + |
| 95 | + if layout::fn_can_unwind(tcx, fn_def_id, sig.abi()) && abi_can_unwind(sig.abi()) { |
| 96 | + // We have detected a call that can possibly leak foreign unwind. |
| 97 | + // |
| 98 | + // Because the function body itself can unwind, we are not aborting this function call |
| 99 | + // upon unwind, so this call can possibly leak foreign unwind into Rust code if the |
| 100 | + // panic runtime linked is panic-abort. |
| 101 | + |
| 102 | + let lint_root = body.source_scopes[terminator.source_info.scope] |
| 103 | + .local_data |
| 104 | + .as_ref() |
| 105 | + .assert_crate_local() |
| 106 | + .lint_root; |
| 107 | + let span = terminator.source_info.span; |
| 108 | + |
| 109 | + tcx.struct_span_lint_hir(FFI_UNWIND_CALLS, lint_root, span, |lint| { |
| 110 | + let msg = match fn_def_id { |
| 111 | + Some(_) => "call to foreign function with FFI-unwind ABI", |
| 112 | + None => "call to function pointer with FFI-unwind ABI", |
| 113 | + }; |
| 114 | + let mut db = lint.build(msg); |
| 115 | + db.span_label(span, msg); |
| 116 | + db.emit(); |
| 117 | + }); |
| 118 | + |
| 119 | + tainted = true; |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + tainted |
| 124 | +} |
| 125 | + |
| 126 | +fn required_panic_strategy(tcx: TyCtxt<'_>, cnum: CrateNum) -> Option<PanicStrategy> { |
| 127 | + assert_eq!(cnum, LOCAL_CRATE); |
| 128 | + |
| 129 | + if tcx.is_panic_runtime(LOCAL_CRATE) { |
| 130 | + return Some(tcx.sess.panic_strategy()); |
| 131 | + } |
| 132 | + |
| 133 | + if tcx.sess.panic_strategy() == PanicStrategy::Abort { |
| 134 | + return Some(PanicStrategy::Abort); |
| 135 | + } |
| 136 | + |
| 137 | + for def_id in tcx.hir().body_owners() { |
| 138 | + if tcx.has_ffi_unwind_calls(def_id) { |
| 139 | + // Given that this crate is compiled in `-C panic=unwind`, the `AbortUnwindingCalls` |
| 140 | + // MIR pass will not be run on FFI-unwind call sites, therefore a foreign exception |
| 141 | + // can enter Rust through these sites. |
| 142 | + // |
| 143 | + // On the other hand, crates compiled with `-C panic=abort` expects that all Rust |
| 144 | + // functions cannot unwind (whether it's caused by Rust panic or foreign exception), |
| 145 | + // and this expectation mismatch can cause unsoundness (#96926). |
| 146 | + // |
| 147 | + // To address this issue, we enforce that if FFI-unwind calls are used in a crate |
| 148 | + // compiled with `panic=unwind`, then the final panic strategy must be `panic=unwind`. |
| 149 | + // This will ensure that no crates will have wrong unwindability assumption. |
| 150 | + // |
| 151 | + // It should be noted that it is okay to link `panic=unwind` into a `panic=abort` |
| 152 | + // program if it contains no FFI-unwind calls. In such case foreign exception can only |
| 153 | + // enter Rust in a `panic=abort` crate, which will lead to an abort. There will also |
| 154 | + // be no exceptions generated from Rust, so the assumption which `panic=abort` crates |
| 155 | + // make, that no Rust function can unwind, indeed holds for crates compiled with |
| 156 | + // `panic=unwind` as well. In such case this function returns `None`, indicating that |
| 157 | + // the crate does not require a particular final panic strategy, and can be freely |
| 158 | + // linked to crates with either strategy (we need such ability for libstd and its |
| 159 | + // dependencies). |
| 160 | + return Some(PanicStrategy::Unwind); |
| 161 | + } |
| 162 | + } |
| 163 | + |
| 164 | + // This crate can be linked with either runtime. |
| 165 | + None |
| 166 | +} |
| 167 | + |
| 168 | +pub(crate) fn provide(providers: &mut Providers) { |
| 169 | + *providers = Providers { has_ffi_unwind_calls, required_panic_strategy, ..*providers }; |
| 170 | +} |
0 commit comments