Skip to main content

rustc_mir_transform/
ffi_unwind_calls.rs

1use rustc_abi::ExternAbi;
2use rustc_ast::InlineAsmOptions;
3use rustc_hir::def_id::{LOCAL_CRATE, LocalDefId};
4use rustc_middle::mir::*;
5use rustc_middle::query::{LocalCrate, Providers};
6use rustc_middle::ty::{self, TyCtxt, layout};
7use rustc_middle::{bug, span_bug};
8use rustc_session::lint::builtin::FFI_UNWIND_CALLS;
9use rustc_target::spec::PanicStrategy;
10use tracing::debug;
11
12use crate::errors;
13
14// Check if the body of this def_id can possibly leak a foreign unwind into Rust code.
15fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool {
16    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_transform/src/ffi_unwind_calls.rs:16",
                        "rustc_mir_transform::ffi_unwind_calls",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_transform/src/ffi_unwind_calls.rs"),
                        ::tracing_core::__macro_support::Option::Some(16u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_mir_transform::ffi_unwind_calls"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("has_ffi_unwind_calls({0:?})",
                                                    local_def_id) as &dyn Value))])
            });
    } else { ; }
};debug!("has_ffi_unwind_calls({local_def_id:?})");
17
18    // Only perform check on functions because constants cannot call FFI functions.
19    let def_id = local_def_id.to_def_id();
20    let kind = tcx.def_kind(def_id);
21    if !kind.is_fn_like() {
22        return false;
23    }
24
25    let body = &*tcx.mir_built(local_def_id).borrow();
26
27    let body_ty = tcx.type_of(def_id).skip_binder();
28    let body_abi = match body_ty.kind() {
29        ty::FnDef(..) => body_ty.fn_sig(tcx).abi(),
30        ty::Closure(..) => ExternAbi::RustCall,
31        ty::CoroutineClosure(..) => ExternAbi::RustCall,
32        ty::Coroutine(..) => ExternAbi::Rust,
33        ty::Error(_) => return false,
34        _ => ::rustc_middle::util::bug::span_bug_fmt(body.span,
    format_args!("unexpected body ty: {0:?}", body_ty))span_bug!(body.span, "unexpected body ty: {:?}", body_ty),
35    };
36    let body_can_unwind = layout::fn_can_unwind(tcx, Some(def_id), body_abi);
37
38    // Foreign unwinds cannot leak past functions that themselves cannot unwind.
39    if !body_can_unwind {
40        return false;
41    }
42
43    let mut tainted = false;
44
45    for block in body.basic_blocks.iter() {
46        if block.is_cleanup {
47            continue;
48        }
49        let Some(terminator) = &block.terminator else { continue };
50
51        if let TerminatorKind::InlineAsm { options, .. } = &terminator.kind {
52            if options.contains(InlineAsmOptions::MAY_UNWIND) {
53                // We have detected an inline asm block that can possibly leak foreign unwind.
54                //
55                // Because the function body itself can unwind, we are not aborting this function call
56                // upon unwind, so this call can possibly leak foreign unwind into Rust code if the
57                // panic runtime linked is panic-abort.
58
59                let lint_root = body.source_scopes[terminator.source_info.scope]
60                    .local_data
61                    .as_ref()
62                    .unwrap_crate_local()
63                    .lint_root;
64                let span = terminator.source_info.span;
65
66                tcx.emit_node_span_lint(
67                    FFI_UNWIND_CALLS,
68                    lint_root,
69                    span,
70                    errors::AsmUnwindCall { span },
71                );
72
73                tainted = true;
74            }
75            continue;
76        }
77
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        // We assume this is true for intrinsics as well.
85        if sig.abi().is_rustic_abi() {
86            continue;
87        };
88
89        let fn_def_id = match ty.kind() {
90            ty::FnPtr(..) => None,
91            &ty::FnDef(def_id, _) => {
92                // Rust calls cannot themselves create foreign unwinds (even if they use a non-Rust
93                // ABI). So the leak of the foreign unwind into Rust can only be elsewhere, not
94                // here.
95                if !tcx.is_foreign_item(def_id) {
96                    continue;
97                }
98                Some(def_id)
99            }
100            _ => ::rustc_middle::util::bug::bug_fmt(format_args!("invalid callee of type {0:?}",
        ty))bug!("invalid callee of type {:?}", ty),
101        };
102
103        if layout::fn_can_unwind(tcx, fn_def_id, sig.abi()) {
104            // We have detected a call that can possibly leak foreign unwind.
105            //
106            // Because the function body itself can unwind, we are not aborting this function call
107            // upon unwind, so this call can possibly leak foreign unwind into Rust code if the
108            // panic runtime linked is panic-abort.
109
110            let lint_root = body.source_scopes[terminator.source_info.scope]
111                .local_data
112                .as_ref()
113                .unwrap_crate_local()
114                .lint_root;
115            let span = terminator.source_info.span;
116
117            let foreign = fn_def_id.is_some();
118            tcx.emit_node_span_lint(
119                FFI_UNWIND_CALLS,
120                lint_root,
121                span,
122                errors::FfiUnwindCall { span, foreign },
123            );
124
125            tainted = true;
126        }
127    }
128
129    tainted
130}
131
132fn required_panic_strategy(tcx: TyCtxt<'_>, _: LocalCrate) -> Option<PanicStrategy> {
133    let local_strategy = tcx.sess.panic_strategy();
134
135    if tcx.is_panic_runtime(LOCAL_CRATE) {
136        return Some(local_strategy);
137    }
138
139    match local_strategy {
140        PanicStrategy::Abort | PanicStrategy::ImmediateAbort => return Some(local_strategy),
141        _ => {}
142    }
143
144    for def_id in tcx.hir_body_owners() {
145        if tcx.has_ffi_unwind_calls(def_id) {
146            // Given that this crate is compiled in `-C panic=unwind`, the `AbortUnwindingCalls`
147            // MIR pass will not be run on FFI-unwind call sites, therefore a foreign exception
148            // can enter Rust through these sites.
149            //
150            // On the other hand, crates compiled with `-C panic=abort` expects that all Rust
151            // functions cannot unwind (whether it's caused by Rust panic or foreign exception),
152            // and this expectation mismatch can cause unsoundness (#96926).
153            //
154            // To address this issue, we enforce that if FFI-unwind calls are used in a crate
155            // compiled with `panic=unwind`, then the final panic strategy must be `panic=unwind`.
156            // This will ensure that no crates will have wrong unwindability assumption.
157            //
158            // It should be noted that it is okay to link `panic=unwind` into a `panic=abort`
159            // program if it contains no FFI-unwind calls. In such case foreign exception can only
160            // enter Rust in a `panic=abort` crate, which will lead to an abort. There will also
161            // be no exceptions generated from Rust, so the assumption which `panic=abort` crates
162            // make, that no Rust function can unwind, indeed holds for crates compiled with
163            // `panic=unwind` as well. In such case this function returns `None`, indicating that
164            // the crate does not require a particular final panic strategy, and can be freely
165            // linked to crates with either strategy (we need such ability for libstd and its
166            // dependencies).
167            return Some(PanicStrategy::Unwind);
168        }
169    }
170
171    // This crate can be linked with either runtime.
172    None
173}
174
175pub(crate) fn provide(providers: &mut Providers) {
176    *providers = Providers { has_ffi_unwind_calls, required_panic_strategy, ..*providers };
177}