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_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/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 1f6a382175b7c..d0e68e30a6255 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_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 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..909a69bbb25aa --- /dev/null +++ b/compiler/rustc_lint/src/cmse_uninitialized_leak.rs @@ -0,0 +1,183 @@ +use rustc_abi::ExternAbi; +use rustc_hir::{self as hir, Expr, ExprKind}; +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}; + +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 !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::CmseUninitializedMayLeakInformation, + ); + } + } +} + +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 !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 { + Some(tail) => tail.span, + None => expr.span, + }, + _ => expr.span, + } + } else { + expr.span + }; + + // Some part of the source type may be uninitialized. + cx.emit_span_lint( + CMSE_UNINITIALIZED_LEAK, + return_expr_span, + lints::CmseUninitializedMayLeakInformation, + ); + } +} + +/// 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, + } +} 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..840673ca833eb 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_uninitialized_may_leak_information)] +#[note] +pub(crate) struct CmseUninitializedMayLeakInformation; + // precedence.rs #[derive(LintDiagnostic)] #[diag(lint_ambiguous_negative_literals)] 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 { 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..c14079e58bb17 --- /dev/null +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.rs @@ -0,0 +1,76 @@ +//@ 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)] +union ReprRustUnionU64 { + _unused: u64, +} + +#[repr(Rust)] +union ReprRustUnionPartiallyUninit { + _unused1: u32, + _unused2: u16, +} + +#[repr(C)] +union ReprCUnionU64 { + _unused: u64, + _unused1: u32, +} + +#[repr(C)] +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] +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 }); + + f2(ReprCUnionU64 { _unused: 1 }); + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information + + f3(MaybeUninit::uninit()); + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information + + f4(MaybeUninit::uninit()); + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information + + f5((0, MaybeUninit::uninit())); + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information + + f6(ReprCAggregate { a: 0, b: ReprCUnionU64 { _unused: 1 } }); + //~^ 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 new file mode 100644 index 0000000000000..3b922b2cb352e --- /dev/null +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.stderr @@ -0,0 +1,59 @@ +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: 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 (partially) uninitialized value across the security boundary may leak information + --> $DIR/params-uninitialized.rs:59:8 + | +LL | f3(MaybeUninit::uninit()); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = 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:62:8 + | +LL | f4(MaybeUninit::uninit()); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = 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:65:8 + | +LL | f5((0, MaybeUninit::uninit())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = 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:68:8 + | +LL | f6(ReprCAggregate { a: 0, b: ReprCUnionU64 { _unused: 1 } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = 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: 7 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..f9a662a9637ea --- /dev/null +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.rs @@ -0,0 +1,76 @@ +//@ 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)] +union ReprRustUnionU64 { + _unused: u64, +} + +#[no_mangle] +#[allow(improper_ctypes_definitions)] +extern "cmse-nonsecure-entry" fn union_rust() -> ReprRustUnionU64 { + // With `repr(Rust)` value is always fully initialized. + ReprRustUnionU64 { _unused: 1 } +} + +#[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] +extern "cmse-nonsecure-entry" fn maybe_uninit_32bit() -> MaybeUninit { + MaybeUninit::uninit() + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information +} + +#[no_mangle] +extern "cmse-nonsecure-entry" fn maybe_uninit_64bit() -> MaybeUninit { + if true { + return MaybeUninit::new(6.28); + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information + } + MaybeUninit::new(3.14) + //~^ WARN passing a (partially) uninitialized value across the security boundary may leak information +} + +#[repr(transparent)] +struct Wrapper(MaybeUninit); + +#[no_mangle] +extern "cmse-nonsecure-entry" fn repr_transparent_union() -> Wrapper { + match 0 { + //~^ 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 new file mode 100644 index 0000000000000..5bc6a8292a23f --- /dev/null +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.stderr @@ -0,0 +1,55 @@ +warning: passing a (partially) uninitialized value across the security boundary may leak information + --> $DIR/return-uninitialized.rs:33:5 + | +LL | ReprRustUnionPartiallyUninit { _unused1: 1 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = 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 (partially) uninitialized value across the security boundary may leak information + --> $DIR/return-uninitialized.rs:39:5 + | +LL | MaybeUninit::uninit() + | ^^^^^^^^^^^^^^^^^^^^^ + | + = 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:49:5 + | +LL | MaybeUninit::new(3.14) + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = 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:46:9 + | +LL | return MaybeUninit::new(6.28); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = 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:58:5 + | +LL | / match 0 { +LL | | +LL | | 0 => Wrapper(MaybeUninit::new(0)), +LL | | _ => Wrapper(MaybeUninit::new(1)), +LL | | } + | |_____^ + | + = 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 + 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`.