|
| 1 | +use rustc_hir::def::DefKind; |
| 2 | +use rustc_hir::def_id::LocalDefId; |
| 3 | +use rustc_middle::mir::*; |
| 4 | +use rustc_middle::ty::layout; |
| 5 | +use rustc_middle::ty::query::Providers; |
| 6 | +use rustc_middle::ty::{self, TyCtxt}; |
| 7 | +use rustc_session::lint::builtin::FFI_UNWIND_CALLS; |
| 8 | +use rustc_target::spec::abi::Abi; |
| 9 | +use rustc_target::spec::PanicStrategy; |
| 10 | + |
| 11 | +fn abi_can_unwind(abi: Abi) -> bool { |
| 12 | + use Abi::*; |
| 13 | + match abi { |
| 14 | + C { unwind } |
| 15 | + | System { unwind } |
| 16 | + | Cdecl { unwind } |
| 17 | + | Stdcall { unwind } |
| 18 | + | Fastcall { unwind } |
| 19 | + | Vectorcall { unwind } |
| 20 | + | Thiscall { unwind } |
| 21 | + | Aapcs { unwind } |
| 22 | + | Win64 { unwind } |
| 23 | + | SysV64 { unwind } => unwind, |
| 24 | + PtxKernel |
| 25 | + | Msp430Interrupt |
| 26 | + | X86Interrupt |
| 27 | + | AmdGpuKernel |
| 28 | + | EfiApi |
| 29 | + | AvrInterrupt |
| 30 | + | AvrNonBlockingInterrupt |
| 31 | + | CCmseNonSecureCall |
| 32 | + | Wasm |
| 33 | + | RustIntrinsic |
| 34 | + | PlatformIntrinsic |
| 35 | + | Unadjusted => false, |
| 36 | + Rust | RustCall | RustCold => true, |
| 37 | + } |
| 38 | +} |
| 39 | + |
| 40 | +// Check if the body of this def_id can possibly leak a foreign unwind into Rust code. |
| 41 | +fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool { |
| 42 | + debug!("has_ffi_unwind_calls({local_def_id:?})"); |
| 43 | + |
| 44 | + // Only perform check on functions because constants cannot call FFI functions. |
| 45 | + let def_id = local_def_id.to_def_id(); |
| 46 | + let kind = tcx.def_kind(def_id); |
| 47 | + let is_function = match kind { |
| 48 | + DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true, |
| 49 | + _ => tcx.is_closure(def_id), |
| 50 | + }; |
| 51 | + if !is_function { |
| 52 | + return false; |
| 53 | + } |
| 54 | + |
| 55 | + let body = &*tcx.mir_built(ty::WithOptConstParam::unknown(local_def_id)).borrow(); |
| 56 | + |
| 57 | + let body_ty = tcx.type_of(def_id); |
| 58 | + let body_abi = match body_ty.kind() { |
| 59 | + ty::FnDef(..) => body_ty.fn_sig(tcx).abi(), |
| 60 | + ty::Closure(..) => Abi::RustCall, |
| 61 | + ty::Generator(..) => Abi::Rust, |
| 62 | + _ => span_bug!(body.span, "unexpected body ty: {:?}", body_ty), |
| 63 | + }; |
| 64 | + let body_can_unwind = layout::fn_can_unwind(tcx, Some(def_id), body_abi); |
| 65 | + |
| 66 | + // Foreign unwinds cannot leak past functions that themselves cannot unwind. |
| 67 | + if !body_can_unwind { |
| 68 | + return false; |
| 69 | + } |
| 70 | + |
| 71 | + let mut tainted = false; |
| 72 | + |
| 73 | + for block in body.basic_blocks() { |
| 74 | + if block.is_cleanup { |
| 75 | + continue; |
| 76 | + } |
| 77 | + let Some(terminator) = &block.terminator else { continue }; |
| 78 | + let TerminatorKind::Call { func, .. } = &terminator.kind else { continue }; |
| 79 | + |
| 80 | + let ty = func.ty(body, tcx); |
| 81 | + let sig = ty.fn_sig(tcx); |
| 82 | + |
| 83 | + // Rust calls cannot themselves create foreign unwinds. |
| 84 | + if let Abi::Rust | Abi::RustCall | Abi::RustCold = sig.abi() { |
| 85 | + continue; |
| 86 | + }; |
| 87 | + |
| 88 | + let fn_def_id = match ty.kind() { |
| 89 | + ty::FnPtr(_) => None, |
| 90 | + &ty::FnDef(def_id, _) => { |
| 91 | + // Rust calls cannot themselves create foreign unwinds. |
| 92 | + if !tcx.is_foreign_item(def_id) { |
| 93 | + continue; |
| 94 | + } |
| 95 | + Some(def_id) |
| 96 | + } |
| 97 | + _ => bug!("invalid callee of type {:?}", ty), |
| 98 | + }; |
| 99 | + |
| 100 | + if layout::fn_can_unwind(tcx, fn_def_id, sig.abi()) && abi_can_unwind(sig.abi()) { |
| 101 | + // We have detected a call that can possibly leak foreign unwind. |
| 102 | + // |
| 103 | + // Because the function body itself can unwind, we are not aborting this function call |
| 104 | + // upon unwind, so this call can possibly leak foreign unwind into Rust code if the |
| 105 | + // panic runtime linked is panic-abort. |
| 106 | + |
| 107 | + let lint_root = body.source_scopes[terminator.source_info.scope] |
| 108 | + .local_data |
| 109 | + .as_ref() |
| 110 | + .assert_crate_local() |
| 111 | + .lint_root; |
| 112 | + let span = terminator.source_info.span; |
| 113 | + |
| 114 | + tcx.struct_span_lint_hir(FFI_UNWIND_CALLS, lint_root, span, |lint| { |
| 115 | + let msg = match fn_def_id { |
| 116 | + Some(_) => "call to foreign function with FFI-unwind ABI", |
| 117 | + None => "call to function pointer with FFI-unwind ABI", |
| 118 | + }; |
| 119 | + let mut db = lint.build(msg); |
| 120 | + db.span_label(span, msg); |
| 121 | + db.emit(); |
| 122 | + }); |
| 123 | + |
| 124 | + tainted = true; |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + tainted |
| 129 | +} |
| 130 | + |
| 131 | +fn required_panic_strategy(tcx: TyCtxt<'_>, (): ()) -> Option<PanicStrategy> { |
| 132 | + if tcx.sess.panic_strategy() == PanicStrategy::Abort { |
| 133 | + return Some(PanicStrategy::Abort); |
| 134 | + } |
| 135 | + |
| 136 | + for def_id in tcx.hir().body_owners() { |
| 137 | + if tcx.has_ffi_unwind_calls(def_id) { |
| 138 | + return Some(PanicStrategy::Unwind); |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + // This crate can be linked with either runtime. |
| 143 | + None |
| 144 | +} |
| 145 | + |
| 146 | +pub(crate) fn provide(providers: &mut Providers) { |
| 147 | + *providers = Providers { has_ffi_unwind_calls, required_panic_strategy, ..*providers }; |
| 148 | +} |
0 commit comments