|
| 1 | +use rustc_abi::ExternAbi; |
| 2 | +use rustc_hir::{self as hir, Expr, ExprKind}; |
| 3 | +use rustc_middle::ty::layout::{LayoutCx, TyAndLayout}; |
| 4 | +use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt}; |
| 5 | +use rustc_session::{declare_lint, declare_lint_pass}; |
| 6 | + |
| 7 | +use crate::{LateContext, LateLintPass, LintContext, lints}; |
| 8 | + |
| 9 | +declare_lint! { |
| 10 | + /// The `cmse_uninitialized_leak` lint detects values that may be (partially) uninitialized that |
| 11 | + /// cross the secure boundary. |
| 12 | + /// |
| 13 | + /// ### Example |
| 14 | + /// |
| 15 | + /// ```rust,ignore (ABI is only supported on thumbv8) |
| 16 | + /// extern "cmse-nonsecure-entry" fn foo() -> MaybeUninit<u64> { |
| 17 | + /// MaybeUninit::uninit() |
| 18 | + /// } |
| 19 | + /// ``` |
| 20 | + /// |
| 21 | + /// This will produce: |
| 22 | + /// |
| 23 | + /// ``` |
| 24 | + /// warning: passing a union across the security boundary may leak information |
| 25 | + /// --> lint_example.rs:2:5 |
| 26 | + /// | |
| 27 | + /// 2 | MaybeUninit::uninit() |
| 28 | + /// | ^^^^^^^^^^^^^^^^^^^^^ |
| 29 | + /// | |
| 30 | + /// = note: the bits not used by the current variant may contain stale secure data |
| 31 | + /// = note: `#[warn(cmse_uninitialized_leak)]` on by default |
| 32 | + /// ``` |
| 33 | + /// |
| 34 | + /// ### Explanation |
| 35 | + /// |
| 36 | + /// The cmse calling conventions normally take care of clearing registers to make sure that |
| 37 | + /// stale secure information is not observable from non-secure code. Uninitialized memory may |
| 38 | + /// still contain secret information, so the programmer must be careful when (partially) |
| 39 | + /// uninitialized values cross the secure boundary. This lint fires when a partially |
| 40 | + /// uninitialized value (e.g. a `union` value or a type with a niche) crosses the secure |
| 41 | + /// boundary, i.e.: |
| 42 | + /// |
| 43 | + /// - when returned from a `cmse-nonsecure-entry` function |
| 44 | + /// - when passed as an argument to a `cmse-nonsecure-call` function |
| 45 | + /// |
| 46 | + /// This lint is a best effort: not all cases of (partially) uninitialized data crossing the |
| 47 | + /// secure boundary are caught. |
| 48 | + pub CMSE_UNINITIALIZED_LEAK, |
| 49 | + Warn, |
| 50 | + "(partially) uninitialized value may leak secure information" |
| 51 | +} |
| 52 | + |
| 53 | +declare_lint_pass!(CmseUninitializedLeak => [CMSE_UNINITIALIZED_LEAK]); |
| 54 | + |
| 55 | +impl<'tcx> LateLintPass<'tcx> for CmseUninitializedLeak { |
| 56 | + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { |
| 57 | + check_cmse_entry_return(cx, expr); |
| 58 | + check_cmse_call_call(cx, expr); |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +fn check_cmse_call_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { |
| 63 | + let ExprKind::Call(callee, arguments) = expr.kind else { |
| 64 | + return; |
| 65 | + }; |
| 66 | + |
| 67 | + // Determine the callee ABI. |
| 68 | + let callee_ty = cx.typeck_results().expr_ty(callee); |
| 69 | + let sig = match callee_ty.kind() { |
| 70 | + ty::FnPtr(poly_sig, header) if header.abi == ExternAbi::CmseNonSecureCall => { |
| 71 | + poly_sig.skip_binder() |
| 72 | + } |
| 73 | + _ => return, |
| 74 | + }; |
| 75 | + |
| 76 | + let fn_sig = cx.tcx.erase_and_anonymize_regions(sig); |
| 77 | + let typing_env = ty::TypingEnv::fully_monomorphized(); |
| 78 | + |
| 79 | + for (arg, ty) in arguments.iter().zip(fn_sig.inputs()) { |
| 80 | + // `impl Trait` is not allowed in the argument types. |
| 81 | + if ty.has_opaque_types() { |
| 82 | + continue; |
| 83 | + } |
| 84 | + |
| 85 | + let Ok(layout) = cx.tcx.layout_of(typing_env.as_query_input(*ty)) else { |
| 86 | + continue; |
| 87 | + }; |
| 88 | + |
| 89 | + if layout_contains_union(cx.tcx, &layout) { |
| 90 | + cx.emit_span_lint( |
| 91 | + CMSE_UNINITIALIZED_LEAK, |
| 92 | + arg.span, |
| 93 | + lints::CmseUnionMayLeakInformation, |
| 94 | + ); |
| 95 | + } |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +fn check_cmse_entry_return<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { |
| 100 | + let owner = cx.tcx.hir_enclosing_body_owner(expr.hir_id); |
| 101 | + |
| 102 | + match cx.tcx.def_kind(owner) { |
| 103 | + hir::def::DefKind::Fn | hir::def::DefKind::AssocFn => {} |
| 104 | + _ => return, |
| 105 | + } |
| 106 | + |
| 107 | + // Only continue if the current expr is an (implicit) return. |
| 108 | + let body = cx.tcx.hir_body_owned_by(owner); |
| 109 | + let is_implicit_return = expr.hir_id == body.value.hir_id; |
| 110 | + if !(matches!(expr.kind, ExprKind::Ret(_)) || is_implicit_return) { |
| 111 | + return; |
| 112 | + } |
| 113 | + |
| 114 | + let sig = cx.tcx.fn_sig(owner).skip_binder(); |
| 115 | + if sig.abi() != ExternAbi::CmseNonSecureEntry { |
| 116 | + return; |
| 117 | + } |
| 118 | + |
| 119 | + let fn_sig = cx.tcx.instantiate_bound_regions_with_erased(sig); |
| 120 | + let fn_sig = cx.tcx.erase_and_anonymize_regions(fn_sig); |
| 121 | + let return_type = fn_sig.output(); |
| 122 | + |
| 123 | + // `impl Trait` is not allowed in the return type. |
| 124 | + if return_type.has_opaque_types() { |
| 125 | + return; |
| 126 | + } |
| 127 | + |
| 128 | + let typing_env = ty::TypingEnv::fully_monomorphized(); |
| 129 | + let Ok(ret_layout) = cx.tcx.layout_of(typing_env.as_query_input(return_type)) else { |
| 130 | + return; |
| 131 | + }; |
| 132 | + |
| 133 | + if layout_contains_union(cx.tcx, &ret_layout) { |
| 134 | + let return_expr_span = if is_implicit_return { |
| 135 | + match expr.kind { |
| 136 | + ExprKind::Block(block, _) => match block.expr { |
| 137 | + Some(tail) => tail.span, |
| 138 | + None => expr.span, |
| 139 | + }, |
| 140 | + _ => expr.span, |
| 141 | + } |
| 142 | + } else { |
| 143 | + expr.span |
| 144 | + }; |
| 145 | + |
| 146 | + cx.emit_span_lint( |
| 147 | + CMSE_UNINITIALIZED_LEAK, |
| 148 | + return_expr_span, |
| 149 | + lints::CmseUnionMayLeakInformation, |
| 150 | + ); |
| 151 | + } |
| 152 | +} |
| 153 | + |
| 154 | +/// Check whether any part of the layout is a union, which may contain secure data still. |
| 155 | +fn layout_contains_union<'tcx>(tcx: TyCtxt<'tcx>, layout: &TyAndLayout<'tcx>) -> bool { |
| 156 | + if layout.ty.is_union() { |
| 157 | + return true; |
| 158 | + } |
| 159 | + |
| 160 | + let typing_env = ty::TypingEnv::fully_monomorphized(); |
| 161 | + let cx = LayoutCx::new(tcx, typing_env); |
| 162 | + |
| 163 | + match &layout.variants { |
| 164 | + rustc_abi::Variants::Single { .. } => { |
| 165 | + for i in 0..layout.fields.count() { |
| 166 | + if layout_contains_union(tcx, &layout.field(&cx, i)) { |
| 167 | + return true; |
| 168 | + } |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + rustc_abi::Variants::Multiple { variants, .. } => { |
| 173 | + for (variant_idx, _vdata) in variants.iter_enumerated() { |
| 174 | + let variant_layout = layout.for_variant(&cx, variant_idx); |
| 175 | + |
| 176 | + for i in 0..variant_layout.fields.count() { |
| 177 | + if layout_contains_union(tcx, &variant_layout.field(&cx, i)) { |
| 178 | + return true; |
| 179 | + } |
| 180 | + } |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + rustc_abi::Variants::Empty => {} |
| 185 | + } |
| 186 | + |
| 187 | + false |
| 188 | +} |
0 commit comments