From 2843ea665f77cc2f1fa56c3d504b8e7ca794e539 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 8 Oct 2025 15:29:24 +0200 Subject: [PATCH 1/3] add `MaybeUninit` to `minicore` --- tests/auxiliary/minicore.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index 4f4c653cb46e7..2db1ca9003d7d 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -26,6 +26,7 @@ decl_macro, f16, f128, + transparent_unions, asm_experimental_arch, unboxed_closures )] @@ -119,6 +120,25 @@ pub struct ManuallyDrop { } impl Copy for ManuallyDrop {} +#[lang = "maybe_uninit"] +#[repr(transparent)] +pub union MaybeUninit { + uninit: (), + value: ManuallyDrop, +} + +impl Copy for MaybeUninit {} + +impl MaybeUninit { + pub const fn uninit() -> Self { + Self { uninit: () } + } + + pub const fn new(value: T) -> Self { + Self { value: ManuallyDrop { value } } + } +} + #[lang = "unsafe_cell"] #[repr(transparent)] pub struct UnsafeCell { From 226c73a382b82025c4420e85c46992e2c480a21c Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 26 Sep 2025 14:35:47 +0200 Subject: [PATCH 2/3] cmse: lint when unions cross the security boundary When a union passes from secure to non-secure (so, passed as an argument to an nonsecure call, or returned by a nonsecure entry), warn that there may be secure information lingering in the unused or uninitialized parts of a union value. https://godbolt.org/z/vq9xnrnEs --- .../src/hir_ty_lowering/cmse.rs | 8 +- compiler/rustc_lint/messages.ftl | 4 + .../rustc_lint/src/cmse_uninitialized_leak.rs | 188 ++++++++++++++++++ compiler/rustc_lint/src/lib.rs | 3 + compiler/rustc_lint/src/lints.rs | 6 + .../params-uninitialized.rs | 55 +++++ .../params-uninitialized.stderr | 51 +++++ .../cmse-nonsecure-call/return-via-stack.rs | 7 +- .../return-via-stack.stderr | 11 +- .../cmse-nonsecure-entry/params-via-stack.rs | 23 +++ .../return-uninitialized.rs | 51 +++++ .../return-uninitialized.stderr | 62 ++++++ .../cmse-nonsecure-entry/return-via-stack.rs | 24 +-- .../return-via-stack.stderr | 20 +- 14 files changed, 453 insertions(+), 60 deletions(-) create mode 100644 compiler/rustc_lint/src/cmse_uninitialized_leak.rs create mode 100644 tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.rs create mode 100644 tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.stderr create mode 100644 tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.rs create mode 100644 tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.stderr diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/cmse.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/cmse.rs index f8af6888923ce..03abba133a05d 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/cmse.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/cmse.rs @@ -1,4 +1,4 @@ -use rustc_abi::{BackendRepr, ExternAbi, Float, Integer, Primitive, Scalar}; +use rustc_abi::{BackendRepr, ExternAbi, Float, Integer, Primitive}; use rustc_errors::{DiagCtxtHandle, E0781, struct_span_code_err}; use rustc_hir::{self as hir, HirId}; use rustc_middle::bug; @@ -163,11 +163,7 @@ fn is_valid_cmse_output_layout<'tcx>(layout: TyAndLayout<'tcx>) -> bool { return false; }; - let Scalar::Initialized { value, .. } = scalar else { - return false; - }; - - matches!(value, Primitive::Int(Integer::I64, _) | Primitive::Float(Float::F64)) + matches!(scalar.primitive(), Primitive::Int(Integer::I64, _) | Primitive::Float(Float::F64)) } fn should_emit_layout_error<'tcx>(abi: ExternAbi, layout_err: &'tcx LayoutError<'tcx>) -> bool { diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 1f6a382175b7c..2d0a9835dc46d 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -187,6 +187,10 @@ lint_closure_returning_async_block = closure returning async block can be made i .label = this async block can be removed, and the closure can be turned into an async closure .suggestion = turn this into an async closure +lint_cmse_union_may_leak_information = + passing a union across the security boundary may leak information + .note = the bits not used by the current variant may contain stale secure data + lint_command_line_source = `forbid` lint level was set on command line lint_confusable_identifier_pair = found both `{$existing_sym}` and `{$sym}` as identifiers, which look alike diff --git a/compiler/rustc_lint/src/cmse_uninitialized_leak.rs b/compiler/rustc_lint/src/cmse_uninitialized_leak.rs new file mode 100644 index 0000000000000..16c373ef018a1 --- /dev/null +++ b/compiler/rustc_lint/src/cmse_uninitialized_leak.rs @@ -0,0 +1,188 @@ +use rustc_abi::ExternAbi; +use rustc_hir::{self as hir, Expr, ExprKind}; +use rustc_middle::ty::layout::{LayoutCx, TyAndLayout}; +use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt}; +use rustc_session::{declare_lint, declare_lint_pass}; + +use crate::{LateContext, LateLintPass, LintContext, lints}; + +declare_lint! { + /// The `cmse_uninitialized_leak` lint detects values that may be (partially) uninitialized that + /// cross the secure boundary. + /// + /// ### Example + /// + /// ```rust,ignore (ABI is only supported on thumbv8) + /// extern "cmse-nonsecure-entry" fn foo() -> MaybeUninit { + /// MaybeUninit::uninit() + /// } + /// ``` + /// + /// This will produce: + /// + /// ```text + /// warning: passing a union across the security boundary may leak information + /// --> lint_example.rs:2:5 + /// | + /// 2 | MaybeUninit::uninit() + /// | ^^^^^^^^^^^^^^^^^^^^^ + /// | + /// = note: the bits not used by the current variant may contain stale secure data + /// = note: `#[warn(cmse_uninitialized_leak)]` on by default + /// ``` + /// + /// ### Explanation + /// + /// The cmse calling conventions normally take care of clearing registers to make sure that + /// stale secure information is not observable from non-secure code. Uninitialized memory may + /// still contain secret information, so the programmer must be careful when (partially) + /// uninitialized values cross the secure boundary. This lint fires when a partially + /// uninitialized value (e.g. a `union` value or a type with a niche) crosses the secure + /// boundary, i.e.: + /// + /// - when returned from a `cmse-nonsecure-entry` function + /// - when passed as an argument to a `cmse-nonsecure-call` function + /// + /// This lint is a best effort: not all cases of (partially) uninitialized data crossing the + /// secure boundary are caught. + pub CMSE_UNINITIALIZED_LEAK, + Warn, + "(partially) uninitialized value may leak secure information" +} + +declare_lint_pass!(CmseUninitializedLeak => [CMSE_UNINITIALIZED_LEAK]); + +impl<'tcx> LateLintPass<'tcx> for CmseUninitializedLeak { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + check_cmse_entry_return(cx, expr); + check_cmse_call_call(cx, expr); + } +} + +fn check_cmse_call_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + let ExprKind::Call(callee, arguments) = expr.kind else { + return; + }; + + // Determine the callee ABI. + let callee_ty = cx.typeck_results().expr_ty(callee); + let sig = match callee_ty.kind() { + ty::FnPtr(poly_sig, header) if header.abi == ExternAbi::CmseNonSecureCall => { + poly_sig.skip_binder() + } + _ => return, + }; + + let fn_sig = cx.tcx.erase_and_anonymize_regions(sig); + let typing_env = ty::TypingEnv::fully_monomorphized(); + + for (arg, ty) in arguments.iter().zip(fn_sig.inputs()) { + // `impl Trait` is not allowed in the argument types. + if ty.has_opaque_types() { + continue; + } + + let Ok(layout) = cx.tcx.layout_of(typing_env.as_query_input(*ty)) else { + continue; + }; + + if layout_contains_union(cx.tcx, &layout) { + cx.emit_span_lint( + CMSE_UNINITIALIZED_LEAK, + arg.span, + lints::CmseUnionMayLeakInformation, + ); + } + } +} + +fn check_cmse_entry_return<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + let owner = cx.tcx.hir_enclosing_body_owner(expr.hir_id); + + match cx.tcx.def_kind(owner) { + hir::def::DefKind::Fn | hir::def::DefKind::AssocFn => {} + _ => return, + } + + // Only continue if the current expr is an (implicit) return. + let body = cx.tcx.hir_body_owned_by(owner); + let is_implicit_return = expr.hir_id == body.value.hir_id; + if !(matches!(expr.kind, ExprKind::Ret(_)) || is_implicit_return) { + return; + } + + let sig = cx.tcx.fn_sig(owner).skip_binder(); + if sig.abi() != ExternAbi::CmseNonSecureEntry { + return; + } + + let fn_sig = cx.tcx.instantiate_bound_regions_with_erased(sig); + let fn_sig = cx.tcx.erase_and_anonymize_regions(fn_sig); + let return_type = fn_sig.output(); + + // `impl Trait` is not allowed in the return type. + if return_type.has_opaque_types() { + return; + } + + let typing_env = ty::TypingEnv::fully_monomorphized(); + let Ok(ret_layout) = cx.tcx.layout_of(typing_env.as_query_input(return_type)) else { + return; + }; + + if layout_contains_union(cx.tcx, &ret_layout) { + let return_expr_span = if is_implicit_return { + match expr.kind { + ExprKind::Block(block, _) => match block.expr { + Some(tail) => tail.span, + None => expr.span, + }, + _ => expr.span, + } + } else { + expr.span + }; + + cx.emit_span_lint( + CMSE_UNINITIALIZED_LEAK, + return_expr_span, + lints::CmseUnionMayLeakInformation, + ); + } +} + +/// Check whether any part of the layout is a union, which may contain secure data still. +fn layout_contains_union<'tcx>(tcx: TyCtxt<'tcx>, layout: &TyAndLayout<'tcx>) -> bool { + if layout.ty.is_union() { + return true; + } + + let typing_env = ty::TypingEnv::fully_monomorphized(); + let cx = LayoutCx::new(tcx, typing_env); + + match &layout.variants { + rustc_abi::Variants::Single { .. } => { + for i in 0..layout.fields.count() { + if layout_contains_union(tcx, &layout.field(&cx, i)) { + return true; + } + } + } + + rustc_abi::Variants::Multiple { variants, .. } => { + for (variant_idx, _vdata) in variants.iter_enumerated() { + let variant_layout = layout.for_variant(&cx, variant_idx); + + for i in 0..variant_layout.fields.count() { + if layout_contains_union(tcx, &variant_layout.field(&cx, i)) { + return true; + } + } + } + } + + rustc_abi::Variants::Empty => {} + } + + false +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index faaeb7706e399..57d4ce124b41f 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -37,6 +37,7 @@ mod async_closures; mod async_fn_in_trait; mod autorefs; pub mod builtin; +mod cmse_uninitialized_leak; mod context; mod dangling; mod default_could_be_derived; @@ -86,6 +87,7 @@ use async_closures::AsyncClosureUsage; use async_fn_in_trait::AsyncFnInTrait; use autorefs::*; use builtin::*; +use cmse_uninitialized_leak::*; use dangling::*; use default_could_be_derived::DefaultCouldBeDerived; use deref_into_dyn_supertrait::*; @@ -246,6 +248,7 @@ late_lint_methods!( UnqualifiedLocalImports: UnqualifiedLocalImports, CheckTransmutes: CheckTransmutes, LifetimeSyntax: LifetimeSyntax, + CmseUninitializedLeak: CmseUninitializedLeak, ] ] ); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index c55f2b9dd6f24..caa110e046a2a 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1467,6 +1467,12 @@ pub(crate) struct NonLocalDefinitionsCargoUpdateNote { pub crate_name: Symbol, } +// cmse_uninitialized_leak.rs +#[derive(LintDiagnostic)] +#[diag(lint_cmse_union_may_leak_information)] +#[note] +pub(crate) struct CmseUnionMayLeakInformation; + // precedence.rs #[derive(LintDiagnostic)] #[diag(lint_ambiguous_negative_literals)] diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.rs new file mode 100644 index 0000000000000..8386489fba108 --- /dev/null +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.rs @@ -0,0 +1,55 @@ +//@ add-core-stubs +//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib +//@ needs-llvm-components: arm +//@ check-pass +#![feature(abi_cmse_nonsecure_call, no_core, lang_items)] +#![no_core] +#![allow(improper_ctypes_definitions)] + +extern crate minicore; +use minicore::*; + +#[repr(Rust)] +pub union ReprRustUnionU64 { + _unused: u64, +} + +#[repr(C)] +pub union ReprCUnionU64 { + _unused: u64, + _unused1: u32, +} + +#[repr(C)] +pub struct ReprCAggregate { + a: usize, + b: ReprCUnionU64, +} + +#[no_mangle] +pub fn test_union( + f1: extern "cmse-nonsecure-call" fn(ReprRustUnionU64), + f2: extern "cmse-nonsecure-call" fn(ReprCUnionU64), + f3: extern "cmse-nonsecure-call" fn(MaybeUninit), + f4: extern "cmse-nonsecure-call" fn(MaybeUninit), + f5: extern "cmse-nonsecure-call" fn((usize, MaybeUninit)), + f6: extern "cmse-nonsecure-call" fn(ReprCAggregate), +) { + f1(ReprRustUnionU64 { _unused: 1 }); + //~^ WARN passing a union across the security boundary may leak information + + f2(ReprCUnionU64 { _unused: 1 }); + //~^ WARN passing a union across the security boundary may leak information + + f3(MaybeUninit::uninit()); + //~^ WARN passing a union across the security boundary may leak information + + f4(MaybeUninit::uninit()); + //~^ WARN passing a union across the security boundary may leak information + + f5((0, MaybeUninit::uninit())); + //~^ WARN passing a union across the security boundary may leak information + + f6(ReprCAggregate { a: 0, b: ReprCUnionU64 { _unused: 1 } }); + //~^ WARN passing a union across the security boundary may leak information +} diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.stderr new file mode 100644 index 0000000000000..8d80e5993dfdc --- /dev/null +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.stderr @@ -0,0 +1,51 @@ +warning: passing a union across the security boundary may leak information + --> $DIR/params-uninitialized.rs:38:8 + | +LL | f1(ReprRustUnionU64 { _unused: 1 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the bits not used by the current variant may contain stale secure data + = note: `#[warn(cmse_uninitialized_leak)]` on by default + +warning: passing a union across the security boundary may leak information + --> $DIR/params-uninitialized.rs:41:8 + | +LL | f2(ReprCUnionU64 { _unused: 1 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the bits not used by the current variant may contain stale secure data + +warning: passing a union across the security boundary may leak information + --> $DIR/params-uninitialized.rs:44:8 + | +LL | f3(MaybeUninit::uninit()); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: the bits not used by the current variant may contain stale secure data + +warning: passing a union across the security boundary may leak information + --> $DIR/params-uninitialized.rs:47:8 + | +LL | f4(MaybeUninit::uninit()); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: the bits not used by the current variant may contain stale secure data + +warning: passing a union across the security boundary may leak information + --> $DIR/params-uninitialized.rs:50:8 + | +LL | f5((0, MaybeUninit::uninit())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the bits not used by the current variant may contain stale secure data + +warning: passing a union across the security boundary may leak information + --> $DIR/params-uninitialized.rs:53:8 + | +LL | f6(ReprCAggregate { a: 0, b: ReprCUnionU64 { _unused: 1 } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the bits not used by the current variant may contain stale secure data + +warning: 6 warnings emitted + diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs index 77347b04ede83..267e3b9baed07 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs @@ -49,7 +49,10 @@ pub union ReprRustUnionU64 { #[no_mangle] pub fn test_union( - f1: extern "cmse-nonsecure-call" fn() -> ReprRustUnionU64, //~ ERROR [E0798] - f2: extern "cmse-nonsecure-call" fn() -> ReprCUnionU64, //~ ERROR [E0798] + f1: extern "cmse-nonsecure-call" fn() -> ReprRustUnionU64, + f2: extern "cmse-nonsecure-call" fn() -> ReprCUnionU64, + //~^ ERROR return value of `"cmse-nonsecure-call"` function too large to pass via registers [E0798] + f3: extern "cmse-nonsecure-call" fn() -> MaybeUninit, + f4: extern "cmse-nonsecure-call" fn() -> MaybeUninit, ) { } diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.stderr index ddf969c1bce1b..6849920b900cf 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.stderr +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.stderr @@ -61,15 +61,6 @@ LL | f5: extern "cmse-nonsecure-call" fn() -> [u8; 5], = note: functions with the `"cmse-nonsecure-call"` ABI must pass their result via the available return registers = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size -error[E0798]: return value of `"cmse-nonsecure-call"` function too large to pass via registers - --> $DIR/return-via-stack.rs:52:46 - | -LL | f1: extern "cmse-nonsecure-call" fn() -> ReprRustUnionU64, - | ^^^^^^^^^^^^^^^^ this type doesn't fit in the available registers - | - = note: functions with the `"cmse-nonsecure-call"` ABI must pass their result via the available return registers - = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size - error[E0798]: return value of `"cmse-nonsecure-call"` function too large to pass via registers --> $DIR/return-via-stack.rs:53:46 | @@ -79,6 +70,6 @@ LL | f2: extern "cmse-nonsecure-call" fn() -> ReprCUnionU64, = note: functions with the `"cmse-nonsecure-call"` ABI must pass their result via the available return registers = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size -error: aborting due to 9 previous errors +error: aborting due to 8 previous errors For more information about this error, try `rustc --explain E0798`. diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/params-via-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/params-via-stack.rs index d4f722fa1938b..2897bf86fc3bb 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/params-via-stack.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/params-via-stack.rs @@ -23,3 +23,26 @@ pub extern "cmse-nonsecure-entry" fn f4(_: AlignRelevant, _: u32) {} //~ ERROR [ #[no_mangle] #[allow(improper_ctypes_definitions)] pub extern "cmse-nonsecure-entry" fn f5(_: [u32; 5]) {} //~ ERROR [E0798] + +#[repr(Rust)] +pub union ReprRustUnionU64 { + _unused: u64, +} + +#[repr(C)] +pub union ReprCUnionU64 { + _unused: u64, +} + +#[no_mangle] +#[allow(improper_ctypes_definitions)] +pub extern "cmse-nonsecure-entry" fn union_rust(_: ReprRustUnionU64) {} + +#[no_mangle] +pub extern "cmse-nonsecure-entry" fn union_c(_: ReprCUnionU64) {} + +#[no_mangle] +pub extern "cmse-nonsecure-entry" fn maybe_uninit_32bit(_: MaybeUninit) {} + +#[no_mangle] +pub extern "cmse-nonsecure-entry" fn maybe_uninit_64bit(_: MaybeUninit) {} diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.rs new file mode 100644 index 0000000000000..02848031f11c8 --- /dev/null +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.rs @@ -0,0 +1,51 @@ +//@ add-core-stubs +//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib +//@ needs-llvm-components: arm +//@ check-pass + +#![feature(cmse_nonsecure_entry, no_core, lang_items)] +#![no_core] + +extern crate minicore; +use minicore::*; + +#[repr(Rust)] +pub union ReprRustUnionU64 { + _unused: u64, +} + +#[no_mangle] +#[allow(improper_ctypes_definitions)] +pub extern "cmse-nonsecure-entry" fn union_rust() -> ReprRustUnionU64 { + ReprRustUnionU64 { _unused: 1 } + //~^ WARN passing a union across the security boundary may leak information +} + +#[no_mangle] +pub extern "cmse-nonsecure-entry" fn maybe_uninit_32bit() -> MaybeUninit { + MaybeUninit::uninit() + //~^ WARN passing a union across the security boundary may leak information +} + +#[no_mangle] +pub extern "cmse-nonsecure-entry" fn maybe_uninit_64bit() -> MaybeUninit { + if true { + return MaybeUninit::new(6.28); + //~^ WARN passing a union across the security boundary may leak information + } + MaybeUninit::new(3.14) + //~^ WARN passing a union across the security boundary may leak information +} + +#[repr(transparent)] +pub struct Wrapper(ReprRustUnionU64); + +#[no_mangle] +pub extern "cmse-nonsecure-entry" fn repr_transparent_union() -> Wrapper { + //~^ WARN improper_ctypes_definitions + match 0 { + //~^ WARN passing a union across the security boundary may leak information + 0 => Wrapper(ReprRustUnionU64 { _unused: 1 }), + _ => Wrapper(ReprRustUnionU64 { _unused: 2 }), + } +} diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.stderr new file mode 100644 index 0000000000000..08fed72135104 --- /dev/null +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.stderr @@ -0,0 +1,62 @@ +warning: passing a union across the security boundary may leak information + --> $DIR/return-uninitialized.rs:20:5 + | +LL | ReprRustUnionU64 { _unused: 1 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the bits not used by the current variant may contain stale secure data + = note: `#[warn(cmse_uninitialized_leak)]` on by default + +warning: passing a union across the security boundary may leak information + --> $DIR/return-uninitialized.rs:26:5 + | +LL | MaybeUninit::uninit() + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: the bits not used by the current variant may contain stale secure data + +warning: passing a union across the security boundary may leak information + --> $DIR/return-uninitialized.rs:36:5 + | +LL | MaybeUninit::new(3.14) + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the bits not used by the current variant may contain stale secure data + +warning: passing a union across the security boundary may leak information + --> $DIR/return-uninitialized.rs:33:9 + | +LL | return MaybeUninit::new(6.28); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the bits not used by the current variant may contain stale secure data + +warning: `extern` fn uses type `ReprRustUnionU64`, which is not FFI-safe + --> $DIR/return-uninitialized.rs:44:66 + | +LL | pub extern "cmse-nonsecure-entry" fn repr_transparent_union() -> Wrapper { + | ^^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union + = note: this union has unspecified layout +note: the type is defined here + --> $DIR/return-uninitialized.rs:13:1 + | +LL | pub union ReprRustUnionU64 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `#[warn(improper_ctypes_definitions)]` on by default + +warning: passing a union across the security boundary may leak information + --> $DIR/return-uninitialized.rs:46:5 + | +LL | / match 0 { +LL | | +LL | | 0 => Wrapper(ReprRustUnionU64 { _unused: 1 }), +LL | | _ => Wrapper(ReprRustUnionU64 { _unused: 2 }), +LL | | } + | |_____^ + | + = note: the bits not used by the current variant may contain stale secure data + +warning: 6 warnings emitted + diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.rs index 0052a0977ed71..227b629594bf8 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.rs @@ -1,10 +1,10 @@ //@ add-core-stubs //@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib //@ needs-llvm-components: arm -//@ add-core-stubs #![feature(cmse_nonsecure_entry, no_core, lang_items)] #![no_core] +#![warn(cmse_uninitialized_leak)] extern crate minicore; use minicore::*; @@ -60,25 +60,3 @@ pub extern "cmse-nonsecure-entry" fn i128() -> i128 { //~^ ERROR [E0798] 456 } - -#[repr(Rust)] -pub union ReprRustUnionU64 { - _unused: u64, -} - -#[repr(C)] -pub union ReprCUnionU64 { - _unused: u64, -} - -#[no_mangle] -#[allow(improper_ctypes_definitions)] -pub extern "cmse-nonsecure-entry" fn union_rust() -> ReprRustUnionU64 { - //~^ ERROR [E0798] - ReprRustUnionU64 { _unused: 1 } -} -#[no_mangle] -pub extern "cmse-nonsecure-entry" fn union_c() -> ReprCUnionU64 { - //~^ ERROR [E0798] - ReprCUnionU64 { _unused: 2 } -} diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.stderr index c5effed92ae92..80e6e9140c1e5 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.stderr +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.stderr @@ -61,24 +61,6 @@ LL | pub extern "cmse-nonsecure-entry" fn i128() -> i128 { = note: functions with the `"cmse-nonsecure-entry"` ABI must pass their result via the available return registers = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size -error[E0798]: return value of `"cmse-nonsecure-entry"` function too large to pass via registers - --> $DIR/return-via-stack.rs:76:54 - | -LL | pub extern "cmse-nonsecure-entry" fn union_rust() -> ReprRustUnionU64 { - | ^^^^^^^^^^^^^^^^ this type doesn't fit in the available registers - | - = note: functions with the `"cmse-nonsecure-entry"` ABI must pass their result via the available return registers - = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size - -error[E0798]: return value of `"cmse-nonsecure-entry"` function too large to pass via registers - --> $DIR/return-via-stack.rs:81:51 - | -LL | pub extern "cmse-nonsecure-entry" fn union_c() -> ReprCUnionU64 { - | ^^^^^^^^^^^^^ this type doesn't fit in the available registers - | - = note: functions with the `"cmse-nonsecure-entry"` ABI must pass their result via the available return registers - = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size - -error: aborting due to 9 previous errors +error: aborting due to 7 previous errors For more information about this error, try `rustc --explain E0798`. From 5abb0ffe91e2f87d2058b57a9a095a3afec5f4da Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 29 Oct 2025 20:10:28 +0100 Subject: [PATCH 3/3] cmse: lint when a (partially) uninitialized value crosses the secure boundary --- Cargo.lock | 1 + compiler/rustc_lint/Cargo.toml | 1 + compiler/rustc_lint/messages.ftl | 6 +- .../rustc_lint/src/cmse_uninitialized_leak.rs | 73 +++++++++---------- compiler/rustc_lint/src/lints.rs | 4 +- .../params-uninitialized.rs | 41 ++++++++--- .../params-uninitialized.stderr | 58 ++++++++------- .../return-uninitialized.rs | 53 ++++++++++---- .../return-uninitialized.stderr | 61 +++++++--------- 9 files changed, 171 insertions(+), 127 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e9ba9b432793..3e7bb723ecaae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4100,6 +4100,7 @@ dependencies = [ "rustc_span", "rustc_target", "rustc_trait_selection", + "rustc_transmute", "smallvec", "tracing", "unicode-security", diff --git a/compiler/rustc_lint/Cargo.toml b/compiler/rustc_lint/Cargo.toml index 3a50aac50cb3e..c01d22654f037 100644 --- a/compiler/rustc_lint/Cargo.toml +++ b/compiler/rustc_lint/Cargo.toml @@ -24,6 +24,7 @@ rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } rustc_target = { path = "../rustc_target" } rustc_trait_selection = { path = "../rustc_trait_selection" } +rustc_transmute = { path = "../rustc_transmute" } smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } tracing = "0.1" unicode-security = "0.1.0" diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 2d0a9835dc46d..d0e68e30a6255 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -187,9 +187,9 @@ lint_closure_returning_async_block = closure returning async block can be made i .label = this async block can be removed, and the closure can be turned into an async closure .suggestion = turn this into an async closure -lint_cmse_union_may_leak_information = - passing a union across the security boundary may leak information - .note = the bits not used by the current variant may contain stale secure data +lint_cmse_uninitialized_may_leak_information = + passing a (partially) uninitialized value across the security boundary may leak information + .note = padding or fields not used by the current variant of a union may contain stale secure data lint_command_line_source = `forbid` lint level was set on command line diff --git a/compiler/rustc_lint/src/cmse_uninitialized_leak.rs b/compiler/rustc_lint/src/cmse_uninitialized_leak.rs index 16c373ef018a1..909a69bbb25aa 100644 --- a/compiler/rustc_lint/src/cmse_uninitialized_leak.rs +++ b/compiler/rustc_lint/src/cmse_uninitialized_leak.rs @@ -1,7 +1,7 @@ use rustc_abi::ExternAbi; use rustc_hir::{self as hir, Expr, ExprKind}; -use rustc_middle::ty::layout::{LayoutCx, TyAndLayout}; -use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::layout::TyAndLayout; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; use rustc_session::{declare_lint, declare_lint_pass}; use crate::{LateContext, LateLintPass, LintContext, lints}; @@ -86,11 +86,12 @@ fn check_cmse_call_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { continue; }; - if layout_contains_union(cx.tcx, &layout) { + if !is_transmutable_to_array_u8(cx.tcx, ty.clone(), layout) { + // Some part of the source type may be uninitialized. cx.emit_span_lint( CMSE_UNINITIALIZED_LEAK, arg.span, - lints::CmseUnionMayLeakInformation, + lints::CmseUninitializedMayLeakInformation, ); } } @@ -126,11 +127,12 @@ fn check_cmse_entry_return<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) } let typing_env = ty::TypingEnv::fully_monomorphized(); + let Ok(ret_layout) = cx.tcx.layout_of(typing_env.as_query_input(return_type)) else { return; }; - if layout_contains_union(cx.tcx, &ret_layout) { + if !is_transmutable_to_array_u8(cx.tcx, return_type.clone(), ret_layout) { let return_expr_span = if is_implicit_return { match expr.kind { ExprKind::Block(block, _) => match block.expr { @@ -143,46 +145,39 @@ fn check_cmse_entry_return<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) expr.span }; + // Some part of the source type may be uninitialized. cx.emit_span_lint( CMSE_UNINITIALIZED_LEAK, return_expr_span, - lints::CmseUnionMayLeakInformation, + lints::CmseUninitializedMayLeakInformation, ); } } -/// Check whether any part of the layout is a union, which may contain secure data still. -fn layout_contains_union<'tcx>(tcx: TyCtxt<'tcx>, layout: &TyAndLayout<'tcx>) -> bool { - if layout.ty.is_union() { - return true; - } - - let typing_env = ty::TypingEnv::fully_monomorphized(); - let cx = LayoutCx::new(tcx, typing_env); - - match &layout.variants { - rustc_abi::Variants::Single { .. } => { - for i in 0..layout.fields.count() { - if layout_contains_union(tcx, &layout.field(&cx, i)) { - return true; - } - } - } - - rustc_abi::Variants::Multiple { variants, .. } => { - for (variant_idx, _vdata) in variants.iter_enumerated() { - let variant_layout = layout.for_variant(&cx, variant_idx); - - for i in 0..variant_layout.fields.count() { - if layout_contains_union(tcx, &variant_layout.field(&cx, i)) { - return true; - } - } - } - } - - rustc_abi::Variants::Empty => {} +/// Check whether the source type `T` can be safely transmuted to `[u8; size_of::()]`. +/// +/// If the transmute is valid, `T` must be fully initialized. Otherwise, parts of it may be +/// uninitialized, which should trigger the lint defined by this module. +fn is_transmutable_to_array_u8<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + layout: TyAndLayout<'tcx>, +) -> bool { + use rustc_transmute::{Answer, Assume, TransmuteTypeEnv, Types}; + + let mut transmute_env = TransmuteTypeEnv::new(tcx); + let assume = Assume { alignment: true, lifetimes: true, safety: false, validity: false }; + + let array_u8_ty = Ty::new_array_with_const_len( + tcx, + tcx.types.u8, + ty::Const::from_target_usize(tcx, layout.size.bytes()), + ); + + let types = Types { src: ty, dst: array_u8_ty }; + + match transmute_env.is_transmutable(types, assume) { + Answer::Yes => true, + Answer::No(_) | Answer::If(_) => false, } - - false } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index caa110e046a2a..840673ca833eb 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1469,9 +1469,9 @@ pub(crate) struct NonLocalDefinitionsCargoUpdateNote { // cmse_uninitialized_leak.rs #[derive(LintDiagnostic)] -#[diag(lint_cmse_union_may_leak_information)] +#[diag(lint_cmse_uninitialized_may_leak_information)] #[note] -pub(crate) struct CmseUnionMayLeakInformation; +pub(crate) struct CmseUninitializedMayLeakInformation; // precedence.rs #[derive(LintDiagnostic)] diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.rs index 8386489fba108..c14079e58bb17 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.rs @@ -10,46 +10,67 @@ extern crate minicore; use minicore::*; #[repr(Rust)] -pub union ReprRustUnionU64 { +union ReprRustUnionU64 { _unused: u64, } +#[repr(Rust)] +union ReprRustUnionPartiallyUninit { + _unused1: u32, + _unused2: u16, +} + #[repr(C)] -pub union ReprCUnionU64 { +union ReprCUnionU64 { _unused: u64, _unused1: u32, } #[repr(C)] -pub struct ReprCAggregate { +struct ReprCAggregate { a: usize, b: ReprCUnionU64, } +// This is an aggregate that cannot be unwrapped, and has 1 (uninitialized) padding byte. +#[repr(C, align(4))] +struct PaddedStruct { + a: u8, + b: u16, +} + #[no_mangle] -pub fn test_union( +fn test_uninitialized( f1: extern "cmse-nonsecure-call" fn(ReprRustUnionU64), f2: extern "cmse-nonsecure-call" fn(ReprCUnionU64), f3: extern "cmse-nonsecure-call" fn(MaybeUninit), f4: extern "cmse-nonsecure-call" fn(MaybeUninit), f5: extern "cmse-nonsecure-call" fn((usize, MaybeUninit)), f6: extern "cmse-nonsecure-call" fn(ReprCAggregate), + f7: extern "cmse-nonsecure-call" fn(ReprRustUnionPartiallyUninit), + f8: extern "cmse-nonsecure-call" fn(PaddedStruct), ) { + // With `repr(Rust)` this union is always initialized. f1(ReprRustUnionU64 { _unused: 1 }); - //~^ WARN passing a union across the security boundary may leak information f2(ReprCUnionU64 { _unused: 1 }); - //~^ WARN passing a union across the security boundary may leak information + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information f3(MaybeUninit::uninit()); - //~^ WARN passing a union across the security boundary may leak information + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information f4(MaybeUninit::uninit()); - //~^ WARN passing a union across the security boundary may leak information + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information f5((0, MaybeUninit::uninit())); - //~^ WARN passing a union across the security boundary may leak information + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information f6(ReprCAggregate { a: 0, b: ReprCUnionU64 { _unused: 1 } }); - //~^ WARN passing a union across the security boundary may leak information + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information + + f7(ReprRustUnionPartiallyUninit { _unused1: 0 }); + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information + + f8(PaddedStruct { a: 0, b: 0 }); + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information } diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.stderr index 8d80e5993dfdc..3b922b2cb352e 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.stderr +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.stderr @@ -1,51 +1,59 @@ -warning: passing a union across the security boundary may leak information - --> $DIR/params-uninitialized.rs:38:8 - | -LL | f1(ReprRustUnionU64 { _unused: 1 }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the bits not used by the current variant may contain stale secure data - = note: `#[warn(cmse_uninitialized_leak)]` on by default - -warning: passing a union across the security boundary may leak information - --> $DIR/params-uninitialized.rs:41:8 +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/params-uninitialized.rs:56:8 | LL | f2(ReprCUnionU64 { _unused: 1 }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the bits not used by the current variant may contain stale secure data + = note: padding or fields not used by the current variant of a union may contain stale secure data + = note: `#[warn(cmse_uninitialized_leak)]` on by default -warning: passing a union across the security boundary may leak information - --> $DIR/params-uninitialized.rs:44:8 +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/params-uninitialized.rs:59:8 | LL | f3(MaybeUninit::uninit()); | ^^^^^^^^^^^^^^^^^^^^^ | - = note: the bits not used by the current variant may contain stale secure data + = note: padding or fields not used by the current variant of a union may contain stale secure data -warning: passing a union across the security boundary may leak information - --> $DIR/params-uninitialized.rs:47:8 +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/params-uninitialized.rs:62:8 | LL | f4(MaybeUninit::uninit()); | ^^^^^^^^^^^^^^^^^^^^^ | - = note: the bits not used by the current variant may contain stale secure data + = note: padding or fields not used by the current variant of a union may contain stale secure data -warning: passing a union across the security boundary may leak information - --> $DIR/params-uninitialized.rs:50:8 +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/params-uninitialized.rs:65:8 | LL | f5((0, MaybeUninit::uninit())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the bits not used by the current variant may contain stale secure data + = note: padding or fields not used by the current variant of a union may contain stale secure data -warning: passing a union across the security boundary may leak information - --> $DIR/params-uninitialized.rs:53:8 +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/params-uninitialized.rs:68:8 | LL | f6(ReprCAggregate { a: 0, b: ReprCUnionU64 { _unused: 1 } }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the bits not used by the current variant may contain stale secure data + = note: padding or fields not used by the current variant of a union may contain stale secure data + +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/params-uninitialized.rs:71:8 + | +LL | f7(ReprRustUnionPartiallyUninit { _unused1: 0 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: padding or fields not used by the current variant of a union may contain stale secure data + +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/params-uninitialized.rs:74:8 + | +LL | f8(PaddedStruct { a: 0, b: 0 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: padding or fields not used by the current variant of a union may contain stale secure data -warning: 6 warnings emitted +warning: 7 warnings emitted diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.rs index 02848031f11c8..f9a662a9637ea 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.rs @@ -10,42 +10,67 @@ extern crate minicore; use minicore::*; #[repr(Rust)] -pub union ReprRustUnionU64 { +union ReprRustUnionU64 { _unused: u64, } #[no_mangle] #[allow(improper_ctypes_definitions)] -pub extern "cmse-nonsecure-entry" fn union_rust() -> ReprRustUnionU64 { +extern "cmse-nonsecure-entry" fn union_rust() -> ReprRustUnionU64 { + // With `repr(Rust)` value is always fully initialized. ReprRustUnionU64 { _unused: 1 } - //~^ WARN passing a union across the security boundary may leak information +} + +#[repr(Rust)] +union ReprRustUnionPartiallyUninit { + _unused1: u32, + _unused2: u16, +} + +#[no_mangle] +#[allow(improper_ctypes_definitions)] +extern "cmse-nonsecure-entry" fn union_rust_partially_uninit() -> ReprRustUnionPartiallyUninit { + ReprRustUnionPartiallyUninit { _unused1: 1 } + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information } #[no_mangle] -pub extern "cmse-nonsecure-entry" fn maybe_uninit_32bit() -> MaybeUninit { +extern "cmse-nonsecure-entry" fn maybe_uninit_32bit() -> MaybeUninit { MaybeUninit::uninit() - //~^ WARN passing a union across the security boundary may leak information + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information } #[no_mangle] -pub extern "cmse-nonsecure-entry" fn maybe_uninit_64bit() -> MaybeUninit { +extern "cmse-nonsecure-entry" fn maybe_uninit_64bit() -> MaybeUninit { if true { return MaybeUninit::new(6.28); - //~^ WARN passing a union across the security boundary may leak information + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information } MaybeUninit::new(3.14) - //~^ WARN passing a union across the security boundary may leak information + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information } #[repr(transparent)] -pub struct Wrapper(ReprRustUnionU64); +struct Wrapper(MaybeUninit); #[no_mangle] -pub extern "cmse-nonsecure-entry" fn repr_transparent_union() -> Wrapper { - //~^ WARN improper_ctypes_definitions +extern "cmse-nonsecure-entry" fn repr_transparent_union() -> Wrapper { match 0 { - //~^ WARN passing a union across the security boundary may leak information - 0 => Wrapper(ReprRustUnionU64 { _unused: 1 }), - _ => Wrapper(ReprRustUnionU64 { _unused: 2 }), + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information + 0 => Wrapper(MaybeUninit::new(0)), + _ => Wrapper(MaybeUninit::new(1)), } } + +// This is an aggregate that cannot be unwrapped, and has 1 (uninitialized) padding byte. +#[repr(C, align(4))] +struct PaddedStruct { + a: u8, + b: u16, +} + +#[no_mangle] +extern "cmse-nonsecure-entry" fn padded_struct() -> PaddedStruct { + PaddedStruct { a: 0, b: 1 } + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information +} diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.stderr index 08fed72135104..5bc6a8292a23f 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.stderr +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.stderr @@ -1,62 +1,55 @@ -warning: passing a union across the security boundary may leak information - --> $DIR/return-uninitialized.rs:20:5 +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/return-uninitialized.rs:33:5 | -LL | ReprRustUnionU64 { _unused: 1 } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | ReprRustUnionPartiallyUninit { _unused1: 1 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the bits not used by the current variant may contain stale secure data + = note: padding or fields not used by the current variant of a union may contain stale secure data = note: `#[warn(cmse_uninitialized_leak)]` on by default -warning: passing a union across the security boundary may leak information - --> $DIR/return-uninitialized.rs:26:5 +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/return-uninitialized.rs:39:5 | LL | MaybeUninit::uninit() | ^^^^^^^^^^^^^^^^^^^^^ | - = note: the bits not used by the current variant may contain stale secure data + = note: padding or fields not used by the current variant of a union may contain stale secure data -warning: passing a union across the security boundary may leak information - --> $DIR/return-uninitialized.rs:36:5 +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/return-uninitialized.rs:49:5 | LL | MaybeUninit::new(3.14) | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the bits not used by the current variant may contain stale secure data + = note: padding or fields not used by the current variant of a union may contain stale secure data -warning: passing a union across the security boundary may leak information - --> $DIR/return-uninitialized.rs:33:9 +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/return-uninitialized.rs:46:9 | LL | return MaybeUninit::new(6.28); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: the bits not used by the current variant may contain stale secure data + = note: padding or fields not used by the current variant of a union may contain stale secure data -warning: `extern` fn uses type `ReprRustUnionU64`, which is not FFI-safe - --> $DIR/return-uninitialized.rs:44:66 - | -LL | pub extern "cmse-nonsecure-entry" fn repr_transparent_union() -> Wrapper { - | ^^^^^^^ not FFI-safe - | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union - = note: this union has unspecified layout -note: the type is defined here - --> $DIR/return-uninitialized.rs:13:1 - | -LL | pub union ReprRustUnionU64 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: `#[warn(improper_ctypes_definitions)]` on by default - -warning: passing a union across the security boundary may leak information - --> $DIR/return-uninitialized.rs:46:5 +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/return-uninitialized.rs:58:5 | LL | / match 0 { LL | | -LL | | 0 => Wrapper(ReprRustUnionU64 { _unused: 1 }), -LL | | _ => Wrapper(ReprRustUnionU64 { _unused: 2 }), +LL | | 0 => Wrapper(MaybeUninit::new(0)), +LL | | _ => Wrapper(MaybeUninit::new(1)), LL | | } | |_____^ | - = note: the bits not used by the current variant may contain stale secure data + = note: padding or fields not used by the current variant of a union may contain stale secure data + +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/return-uninitialized.rs:74:5 + | +LL | PaddedStruct { a: 0, b: 1 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: padding or fields not used by the current variant of a union may contain stale secure data warning: 6 warnings emitted