diff --git a/compiler/rustc_abi/src/layout/ty.rs b/compiler/rustc_abi/src/layout/ty.rs index 9ca4aa2254547..54d3a5671d3e0 100644 --- a/compiler/rustc_abi/src/layout/ty.rs +++ b/compiler/rustc_abi/src/layout/ty.rs @@ -291,29 +291,42 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { /// This is the "guaranteed" padding. There may be more bytes that are padding for some /// but not all variants of this type; those are not included. /// (E.g. `Option` has no guaranteed padding so the empty range set is returned, but its `None` value still has padding). - pub fn padding_ranges(&self, cx: &C) -> Vec> + pub fn variant_independent_padding_ranges(&self, cx: &C) -> Vec> where Ty: TyAbiInterface<'a, C> + Copy, { let mut data = RangeSet::new(); self.add_data_ranges(cx, Size::ZERO, &mut data); - // Find gaps between the data ranges. - let mut uninit_ranges = Vec::new(); - let mut covered_until = Size::ZERO; - for &(offset, size) in data.0.iter() { - if offset > covered_until { - uninit_ranges.push(covered_until..offset); - } - covered_until = Ord::max(covered_until, offset + size); - } + let mut full = RangeSet::new(); + full.add_range(Size::ZERO, self.size); - // Add trailing padding. - if self.size > covered_until { - uninit_ranges.push(covered_until..self.size); - } + full.difference(&data).0.into_iter().map(|(a, b)| a..b).collect() + } + + /// The ranges of bytes that are padding for *some, but not all*, valid values of this type. + /// + /// These bytes contain data for at least one valid value, but are padding for at least one + /// other valid value. In other words, whether such a byte is initialized depends on the + /// concrete value, hence "value-dependent". This is exactly the padding that is *not* + /// already returned by [`Self::variant_independent_padding_ranges`]. + /// + /// For example, `Option` has no guaranteed padding, but the byte holding the payload is + /// value-dependent padding: it is data for `Some(_)` and padding for `None`. + pub fn variant_dependent_padding_ranges(&self, cx: &C) -> Vec> + where + Ty: TyAbiInterface<'a, C> + Copy, + { + // Bytes that carry data for *at least one* valid value. + let mut maybe_data = RangeSet::new(); + self.add_data_ranges(cx, Size::ZERO, &mut maybe_data); - uninit_ranges + // Bytes that carry data for *every* valid value. + let always_data = self.always_data_ranges(cx, Size::ZERO); + + // A byte is value-dependent padding iff it is data for some value but padding for some + // other value. + maybe_data.difference(&always_data).0.into_iter().map(|(a, b)| a..b).collect() } /// Extend `out` with all ranges of bytes that *may* carry relevant data for values of this type. @@ -361,6 +374,16 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { } }, Variants::Multiple { variants, .. } => { + // The variants do not contain e.g. the discriminant or coroutine upvars. + let FieldsShape::Arbitrary { offsets, in_memory_order: _ } = &self.fields else { + unreachable!("a multi-variant layout should have `Arbitrary` fields") + }; + + for (field, &offset) in offsets.iter_enumerated() { + let field = self.field(cx, field.as_usize()); + field.add_data_ranges(cx, base_offset + offset, out); + } + for variant in variants.indices() { let variant = self.for_variant(cx, variant); variant.add_data_ranges(cx, base_offset, out); @@ -368,4 +391,94 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { } } } + + /// The ranges of bytes that contain data for *every* valid value of this type. + /// + /// This is the dual of [`Self::add_data_ranges`], which returns the bytes that contain data + /// for *some* valid value. + /// + /// The difference is in how enums and unions are handled: there a byte may be data for one + /// variant/field, but padding for another. + fn always_data_ranges(self, cx: &C, base_offset: Size) -> RangeSet + where + Ty: TyAbiInterface<'a, C> + Copy, + { + let mut out = RangeSet::new(); + + if self.is_zst() { + return out; + } + + match &self.variants { + Variants::Empty => { /* done */ } + Variants::Single { index: _ } => match &self.fields { + FieldsShape::Primitive => { + out.add_range(base_offset, self.size); + } + &FieldsShape::Union(field_count) => { + // A byte is data for every value only when it is data for every field. + out = (0..field_count.get()) + .map(|field| self.field(cx, field).always_data_ranges(cx, base_offset)) + .reduce(|acc, f| acc.intersection(&f)) + .unwrap_or(out); + } + &FieldsShape::Array { stride, count } => { + let elem = self.field(cx, 0); + + // For scalars we know there is no padding between the elements, + // so the entire array is a single big data range. + if elem.backend_repr.is_scalar() { + out.add_range(base_offset, elem.size * count); + } else { + // FIXME: this is really inefficient for large arrays. + for idx in 0..count { + let elem = elem.always_data_ranges(cx, base_offset + idx * stride); + for &(offset, size) in elem.0.iter() { + out.add_range(offset, size); + } + } + } + } + FieldsShape::Arbitrary { offsets, in_memory_order: _ } => { + // A byte that is always data for any field is always data for the struct. + for (field, &offset) in offsets.iter_enumerated() { + let field = self.field(cx, field.as_usize()); + let field = field.always_data_ranges(cx, base_offset + offset); + for &(offset, size) in field.0.iter() { + out.add_range(offset, size); + } + } + } + }, + Variants::Multiple { variants, .. } => { + // The variants do not contain e.g. the discriminant or coroutine upvars. + let FieldsShape::Arbitrary { offsets, in_memory_order: _ } = &self.fields else { + unreachable!("a multi-variant layout should have `Arbitrary` fields") + }; + + // The fields are variant-independent and always data. + for (field, &offset) in offsets.iter_enumerated() { + let field = self.field(cx, field.as_usize()); + let field = field.always_data_ranges(cx, base_offset + offset); + for &(offset, size) in field.0.iter() { + out.add_range(offset, size); + } + } + + // Otherwise a byte is data for every value only when it is data for every variant. + if let Some(common) = variants + .indices() + .map(|variant| self.for_variant(cx, variant)) + .map(|variant| variant.always_data_ranges(cx, base_offset)) + .reduce(|acc, f| acc.intersection(&f)) + { + for &(offset, size) in common.0.iter() { + out.add_range(offset, size); + } + } + } + } + + out + } } diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 0173b84a4d9a1..60a9a8e2ca5b4 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -610,7 +610,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // // Returning a value with value-dependent padding will instead trigger a lint. let ret_layout = self.fn_abi.ret.layout; - let uninit_ranges = ret_layout.padding_ranges(bx.cx()); + let uninit_ranges = ret_layout.variant_independent_padding_ranges(bx.cx()); self.zero_byte_ranges(bx, llslot, ret_layout.size, &uninit_ranges); } @@ -1878,7 +1878,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx, llscratch, Size::from_bytes(copy_bytes), - &arg.layout.padding_ranges(bx.cx()), + &arg.layout.variant_independent_padding_ranges(bx.cx()), ); } diff --git a/compiler/rustc_data_structures/src/range_set.rs b/compiler/rustc_data_structures/src/range_set.rs index 514946a0fb2de..c0b44f4f5e42b 100644 --- a/compiler/rustc_data_structures/src/range_set.rs +++ b/compiler/rustc_data_structures/src/range_set.rs @@ -56,4 +56,73 @@ where v.insert(idx, (offset, size)); } } + + pub fn intersection(&self, other: &Self) -> Self { + let (a, b) = (self, other); + let mut out = RangeSet::new(); + + let (mut i, mut j) = (0, 0); + while let (Some(&(a_offset, a_size)), Some(&(b_offset, b_size))) = (a.0.get(i), b.0.get(j)) + { + let (a_end, b_end) = (a_offset + a_size, b_offset + b_size); + + let start = Ord::max(a_offset, b_offset); + let end = Ord::min(a_end, b_end); + + // Add the intersection if nonempty. + if start < end { + out.add_range(start, end - start); + } + + // Advance past whichever range ends first. + // The other may still overlap a later range. + if a_end < b_end { + i += 1; + } else { + j += 1; + } + } + + out + } + + /// The ranges from `self` with any intersection with `other` removed. + pub fn difference(&self, other: &Self) -> Self { + let (a, b) = (self, other); + let mut out = Vec::new(); + + let mut j = 0; + for &(a_offset, a_size) in a.0.iter() { + let mut cursor = a_offset; + let a_end = a_offset + a_size; + + // Skip ranges of `b` that end before this range of `a` begins. + // both sequences are sorted they cannot overlap any later range of `a` either. + while let Some(&(b_offset, b_size)) = b.0.get(j) + && b_offset + b_size <= cursor + { + j += 1; + } + + // Carve out each range of `b` that overlaps this range of `a`. A range of `b` may extend + // past `a_end` and overlap the next range of `a`, so leave `j` pointing at it. + let mut k = j; + while let Some(&(b_offset, b_size)) = b.0.get(k) + && b_offset < a_end + { + if b_offset > cursor { + out.push((cursor, b_offset)); + } + cursor = Ord::max(cursor, b_offset + b_size); + k += 1; + } + + // Keep the remainder of the `a`'s range. + if cursor < a_end { + out.push((cursor, a_end)); + } + } + + Self(out) + } } 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..4c97aa9120046 --- /dev/null +++ b/compiler/rustc_lint/src/cmse_uninitialized_leak.rs @@ -0,0 +1,216 @@ +use rustc_abi::ExternAbi; +use rustc_hir::{self as hir, Expr, ExprKind}; +use rustc_middle::ty::{self, Ty, 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: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + /// --> lint_example.rs:2:5 + /// | + /// 2 | MaybeUninit::uninit() + /// | ^^^^^^^^^^^^^^^^^^^^^ + /// | + /// = note: enum and union values can have variant-dependent padding that 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. However, arguments and + /// return values that cross the secure boundary can contain stale secure data in their + /// padding bytes. + /// + /// The compiler clears all variant-independent padding: bytes that are padding for all valid + /// values of the type. But enum and union values can contain variant-dependent padding: bytes + /// that are padding for some but not all valid values of the type. For instance, `Option` + /// has no padding when `Some(_)` but does have padding when `None`. + /// + /// This lint fires when a type with variant-dependent padding crosses the secure boundary: + /// + /// - when returned from a `cmse-nonsecure-entry` function + /// - when passed as an argument to a `cmse-nonsecure-call` function + 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); + + 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; + } + + if contains_unstable_or_variant_dependent_padding(cx, *ty) { + // 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; + } + + if contains_unstable_or_variant_dependent_padding(cx, return_type) { + 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, + ); + } +} + +/// Traverse `T` for any `union` or `enum`, and check whether it contains any padding that is +/// variant-dependent or unstable. +fn contains_unstable_or_variant_dependent_padding<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, +) -> bool { + let tcx = cx.tcx; + + // Types cross the secure boundary fully monomorphized. + let typing_env = ty::TypingEnv::fully_monomorphized(); + + match ty.kind() { + ty::Adt(adt_def, args) => { + if adt_def.is_union() { + // It is still unclear whether a union value where all fields are equally + // large and allow the same bit patterns can be considered initialized + // (see rust-lang/unsafe-code-guidelines#438), so for now we just warn on + // any union. + return true; + } + + if adt_def.is_enum() { + let repr = adt_def.repr(); + + // A `repr(C)` enum has a stable layout, and we can check whether + // whether it contains variant-dependent padding. + if repr.c() { + let Ok(layout) = tcx.layout_of(typing_env.as_query_input(ty)) else { + // Just be conservative if the layout cannot be computed. + return true; + }; + return !layout.variant_dependent_padding_ranges(cx).is_empty(); + } + } + + // For structs we recurse into the fields. + // A non-repr(C) struct already triggers `improper_ctypes`. + adt_def.all_fields().any(|field| { + let field_ty = tcx.normalize_erasing_regions(typing_env, field.ty(tcx, args)); + contains_unstable_or_variant_dependent_padding(cx, field_ty) + }) + } + + ty::Tuple(elems) => { + // Element types might contain unions or enums. + // + // Passing a tuple already triggers `improper_ctypes`. + elems.iter().any(|elem| contains_unstable_or_variant_dependent_padding(cx, elem)) + } + ty::Array(elem, _) => { + // The element type might contain unions or enums. + // + // Passing an array already triggers `improper_ctypes`. + contains_unstable_or_variant_dependent_padding(cx, *elem) + } + + _ => { + // Other types are either scalar (hence no padding), or behind some kind of indirection. + // + // Note that the system traps when dereferencing a pointer to secure memory while in + // non-secure mode, so passing a value with indirection is useless in practice. + debug_assert!({ + let layout = tcx.layout_of(typing_env.as_query_input(ty)).unwrap(); + !layout.variant_dependent_padding_ranges(cx).is_empty() + }); + + false + } + } +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 0e96b9f118790..eae7707e8aa64 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -33,6 +33,7 @@ mod async_fn_in_trait; mod autorefs; pub mod builtin; mod c_void_returns; +mod cmse_uninitialized_leak; mod context; mod dangling; mod default_could_be_derived; @@ -88,6 +89,7 @@ use async_fn_in_trait::AsyncFnInTrait; use autorefs::*; use builtin::*; use c_void_returns::*; +use cmse_uninitialized_leak::*; use dangling::*; use default_could_be_derived::DefaultCouldBeDerived; use deref_into_dyn_supertrait::*; @@ -270,6 +272,7 @@ late_lint_methods!( CheckTransmutes: CheckTransmutes, LifetimeSyntax: LifetimeSyntax, InternalEqTraitMethodImpls: InternalEqTraitMethodImpls, + CmseUninitializedLeak: CmseUninitializedLeak, ImplicitProvenanceCasts: ImplicitProvenanceCasts, CVoidReturns: CVoidReturns, ] diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index eb82afab13186..93c86e17aba42 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1826,6 +1826,16 @@ pub(crate) struct NonLocalDefinitionsCargoUpdateNote { pub crate_name: Symbol, } +// cmse_uninitialized_leak.rs +#[derive(Diagnostic)] +#[diag( + "this value crossing a secure boundary may contain (partially) uninitialized data which can leak information" +)] +#[note( + "enum and union values can have variant-dependent padding that may contain stale secure data" +)] +pub(crate) struct CmseUninitializedMayLeakInformation; + // precedence.rs #[derive(Diagnostic)] #[diag("`-` has lower precedence than method calls, which might be unexpected")] diff --git a/tests/assembly-llvm/cmse-clear-padding.rs b/tests/assembly-llvm/cmse-clear-padding.rs index b092bf304dcca..8406bac74b89f 100644 --- a/tests/assembly-llvm/cmse-clear-padding.rs +++ b/tests/assembly-llvm/cmse-clear-padding.rs @@ -202,3 +202,80 @@ extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8_uninit_tuple( ) -> (MaybeUninit, MaybeUninit) { unsafe { (mem::transmute(a), mem::transmute(b)) } } + +// Check that the tag does not get cleared. +enum Enum { + A(u16), + B(u16), +} + +// CHECK-LABEL: cmse_ret_enum: +// CHECK: .fnstart +// CHECK-NEXT: .save {r7, lr} +// CHECK-NEXT: push {r7, lr} +// CHECK-NEXT: .setfp r7, sp +// CHECK-NEXT: mov r7, sp +// CHECK-NEXT: pop.w {r7, lr} +// CHECK-NEXT: mov r1, lr +// CHECK-NEXT: mov r2, lr +// CHECK-NEXT: mov r3, lr +// CHECK-NEXT: mov r12, lr +// CHECK-NEXT: msr apsr_nzcvq, lr +// CHECK-NEXT: bxns lr +#[no_mangle] +extern "cmse-nonsecure-entry" fn cmse_ret_enum(x: Enum) -> Enum { + x +} + +// Check that variant-dependent padding is left alone. +enum VariantPadding { + A(u8), + B(u16), +} + +// CHECK-LABEL: cmse_ret_enum_with_variant_padding: +// CHECK: .fnstart +// CHECK-NEXT: .save {r7, lr} +// CHECK-NEXT: push {r7, lr} +// CHECK-NEXT: .setfp r7, sp +// CHECK-NEXT: mov r7, sp +// CHECK-NEXT: pop.w {r7, lr} +// CHECK-NEXT: mov r1, lr +// CHECK-NEXT: mov r2, lr +// CHECK-NEXT: mov r3, lr +// CHECK-NEXT: mov r12, lr +// CHECK-NEXT: msr apsr_nzcvq, lr +// CHECK-NEXT: bxns lr +#[no_mangle] +extern "cmse-nonsecure-entry" fn cmse_ret_enum_with_variant_padding( + x: VariantPadding, +) -> VariantPadding { + x +} + +// Check that variant-independent padding does get cleared. +enum IndependentPadding { + A(WideU8), + B(WideU8), +} + +// CHECK-LABEL: cmse_ret_enum_with_independent_padding: +// CHECK: .fnstart +// CHECK-NEXT: .save {r7, lr} +// CHECK-NEXT: push {r7, lr} +// CHECK-NEXT: .setfp r7, sp +// CHECK-NEXT: mov r7, sp +// CHECK-NEXT: bic r0, r0, #-16777216 +// CHECK-NEXT: pop.w {r7, lr} +// CHECK-NEXT: mov r1, lr +// CHECK-NEXT: mov r2, lr +// CHECK-NEXT: mov r3, lr +// CHECK-NEXT: mov r12, lr +// CHECK-NEXT: msr apsr_nzcvq, lr +// CHECK-NEXT: bxns lr +#[no_mangle] +extern "cmse-nonsecure-entry" fn cmse_ret_enum_with_independent_padding( + x: IndependentPadding, +) -> IndependentPadding { + x +} 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..04f08a3719e47 --- /dev/null +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.rs @@ -0,0 +1,109 @@ +//@ add-minicore +//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib +//@ needs-llvm-components: arm +//@ check-pass +//@ ignore-backends: gcc +#![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, +} + +#[repr(C)] +enum VariantsSameSize { + A(u16), + B(u16), +} + +#[repr(C)] +enum VariantsDifferentSize { + A(u8), + B(u16), +} + +enum Void {} + +#[repr(C)] +enum UninhabitedVariant { + A(Void), + 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), + f9: extern "cmse-nonsecure-call" fn(VariantsSameSize), + f10: extern "cmse-nonsecure-call" fn(VariantsDifferentSize), + f11: extern "cmse-nonsecure-call" fn(UninhabitedVariant), +) { + f1(ReprRustUnionU64 { _unused: 1 }); + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + + f2(ReprCUnionU64 { _unused: 1 }); + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + + f3(MaybeUninit::uninit()); + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + + f4(MaybeUninit::uninit()); + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + + f5((0, MaybeUninit::uninit())); + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + + f6(ReprCAggregate { a: 0, b: ReprCUnionU64 { _unused: 1 } }); + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + + f7(ReprRustUnionPartiallyUninit { _unused1: 0 }); + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + + // This struct only has no value-dependent padding, the guaranteed padding is zeroed. + f8(PaddedStruct { a: 0, b: 0 }); + + // This enum only has no value-dependent padding, the guaranteed padding is zeroed. + f9(VariantsSameSize::A(0)); + + f10(VariantsDifferentSize::A(0)); + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + + f11(UninhabitedVariant::B(0)); + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can 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..add535190d167 --- /dev/null +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.stderr @@ -0,0 +1,75 @@ +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/params-uninitialized.rs:77:8 + | +LL | f1(ReprRustUnionU64 { _unused: 1 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + = note: `#[warn(cmse_uninitialized_leak)]` on by default + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/params-uninitialized.rs:80:8 + | +LL | f2(ReprCUnionU64 { _unused: 1 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/params-uninitialized.rs:83:8 + | +LL | f3(MaybeUninit::uninit()); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/params-uninitialized.rs:86:8 + | +LL | f4(MaybeUninit::uninit()); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/params-uninitialized.rs:89:8 + | +LL | f5((0, MaybeUninit::uninit())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/params-uninitialized.rs:92:8 + | +LL | f6(ReprCAggregate { a: 0, b: ReprCUnionU64 { _unused: 1 } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/params-uninitialized.rs:95:8 + | +LL | f7(ReprRustUnionPartiallyUninit { _unused1: 0 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/params-uninitialized.rs:104:9 + | +LL | f10(VariantsDifferentSize::A(0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/params-uninitialized.rs:107:9 + | +LL | f11(UninhabitedVariant::B(0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: 9 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 a8c69216e2048..a4b0d9626e00b 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 @@ -56,7 +56,6 @@ pub union ReprCUnionU64 { pub fn test_union( f1: extern "cmse-nonsecure-call" fn() -> ReprRustUnionU64, //~ ERROR [E0798] f2: extern "cmse-nonsecure-call" fn() -> ReprCUnionU64, //~ ERROR [E0798] - // MaybeUninit is a transparent union, and hence MaybeUninit is abi-compatible with u64, // and thus allowed as a return type. f3: extern "cmse-nonsecure-call" fn() -> 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..89ebe46f5c783 --- /dev/null +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.rs @@ -0,0 +1,143 @@ +//@ add-minicore +//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib +//@ needs-llvm-components: arm +//@ check-pass +//@ ignore-backends: gcc + +#![feature(cmse_nonsecure_entry, no_core, lang_items)] +#![no_core] + +extern crate minicore; +use minicore::*; + +#[repr(Rust)] +union ReprRustUnionU32 { + _unused: u32, +} + +#[no_mangle] +#[allow(improper_ctypes_definitions)] +extern "cmse-nonsecure-entry" fn union_rust() -> ReprRustUnionU32 { + ReprRustUnionU32 { _unused: 1 } + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can 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 this value crossing a secure boundary may contain (partially) uninitialized data which can leak information +} + +#[no_mangle] +extern "cmse-nonsecure-entry" fn maybe_uninit_32bit() -> MaybeUninit { + MaybeUninit::uninit() + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information +} + +#[no_mangle] +extern "cmse-nonsecure-entry" fn maybe_uninit_64bit() -> MaybeUninit { + if true { + return MaybeUninit::new(6.28); + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + } + MaybeUninit::new(3.14) + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information +} + +#[repr(transparent)] +struct Wrapper(MaybeUninit); + +#[no_mangle] +extern "cmse-nonsecure-entry" fn repr_transparent_union() -> Wrapper { + match 0 { + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can 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 { + // This struct only has no value-dependent padding, the guaranteed padding is zeroed. + PaddedStruct { a: 0, b: 1 } +} + +#[repr(C)] +enum VariantsSameSize { + A(u16), + B(u16), +} + +#[no_mangle] +extern "cmse-nonsecure-entry" fn variants_same_size() -> VariantsSameSize { + // This enum only has no value-dependent padding, the guaranteed padding is zeroed. + VariantsSameSize::A(0) +} + +#[repr(C)] +enum VariantsDifferentSize { + A(u8), + B(u16), +} + +#[no_mangle] +extern "cmse-nonsecure-entry" fn variants_different_size() -> VariantsDifferentSize { + VariantsDifferentSize::A(0) + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information +} + +enum Void {} + +#[repr(C)] +enum UninhabitedVariant { + A(Void), + B(u16), +} + +#[no_mangle] +extern "cmse-nonsecure-entry" fn uninhabited_variant() -> UninhabitedVariant { + UninhabitedVariant::B(0) + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information +} + +#[no_mangle] +extern "cmse-nonsecure-entry" fn variants_same_size_array() -> [VariantsSameSize; 1] { + //~^ WARN improper_ctypes_definitions + // This enum only has no value-dependent padding, the guaranteed padding is zeroed. + [VariantsSameSize::A(0)] +} + +#[no_mangle] +extern "cmse-nonsecure-entry" fn variants_different_size_array() -> [VariantsDifferentSize; 1] { + //~^ WARN improper_ctypes_definitions + [VariantsDifferentSize::A(0)] + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can leak information +} + +#[no_mangle] +extern "cmse-nonsecure-entry" fn variants_same_size_tuple() -> (VariantsSameSize,) { + //~^ WARN improper_ctypes_definitions + // This enum only has no value-dependent padding, the guaranteed padding is zeroed. + (VariantsSameSize::A(0),) +} + +#[no_mangle] +extern "cmse-nonsecure-entry" fn variants_different_size_tuple() -> (VariantsDifferentSize,) { + //~^ WARN improper_ctypes_definitions + (VariantsDifferentSize::A(0),) + //~^ WARN this value crossing a secure boundary may contain (partially) uninitialized data which can 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..b8bf310ea5a04 --- /dev/null +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.stderr @@ -0,0 +1,124 @@ +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/return-uninitialized.rs:21:5 + | +LL | ReprRustUnionU32 { _unused: 1 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + = note: `#[warn(cmse_uninitialized_leak)]` on by default + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/return-uninitialized.rs:34:5 + | +LL | ReprRustUnionPartiallyUninit { _unused1: 1 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/return-uninitialized.rs:40:5 + | +LL | MaybeUninit::uninit() + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/return-uninitialized.rs:50:5 + | +LL | MaybeUninit::new(3.14) + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/return-uninitialized.rs:47:9 + | +LL | return MaybeUninit::new(6.28); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/return-uninitialized.rs:59:5 + | +LL | / match 0 { +LL | | +LL | | 0 => Wrapper(MaybeUninit::new(0)), +LL | | _ => Wrapper(MaybeUninit::new(1)), +LL | | } + | |_____^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/return-uninitialized.rs:99:5 + | +LL | VariantsDifferentSize::A(0) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/return-uninitialized.rs:113:5 + | +LL | UninhabitedVariant::B(0) + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: `extern` fn uses type `[VariantsSameSize; 1]`, which is not FFI-safe + --> $DIR/return-uninitialized.rs:118:64 + | +LL | extern "cmse-nonsecure-entry" fn variants_same_size_array() -> [VariantsSameSize; 1] { + | ^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider passing a pointer to the array + = note: passing raw arrays by value is not FFI-safe + = note: `#[warn(improper_ctypes_definitions)]` on by default + +warning: `extern` fn uses type `[VariantsDifferentSize; 1]`, which is not FFI-safe + --> $DIR/return-uninitialized.rs:125:69 + | +LL | extern "cmse-nonsecure-entry" fn variants_different_size_array() -> [VariantsDifferentSize; 1] { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider passing a pointer to the array + = note: passing raw arrays by value is not FFI-safe + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/return-uninitialized.rs:127:5 + | +LL | [VariantsDifferentSize::A(0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: `extern` fn uses type `(VariantsSameSize,)`, which is not FFI-safe + --> $DIR/return-uninitialized.rs:132:64 + | +LL | extern "cmse-nonsecure-entry" fn variants_same_size_tuple() -> (VariantsSameSize,) { + | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +warning: `extern` fn uses type `(VariantsDifferentSize,)`, which is not FFI-safe + --> $DIR/return-uninitialized.rs:139:69 + | +LL | extern "cmse-nonsecure-entry" fn variants_different_size_tuple() -> (VariantsDifferentSize,) { + | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +warning: this value crossing a secure boundary may contain (partially) uninitialized data which can leak information + --> $DIR/return-uninitialized.rs:141:5 + | +LL | (VariantsDifferentSize::A(0),) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: enum and union values can have variant-dependent padding that may contain stale secure data + +warning: 14 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 bfc8ad4583599..f7309f1b16c56 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 @@ -5,6 +5,7 @@ #![feature(cmse_nonsecure_entry, no_core, lang_items)] #![no_core] +#![warn(cmse_uninitialized_leak)] extern crate minicore; use minicore::*; @@ -60,25 +61,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..41d9b56dab63b 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 @@ -1,5 +1,5 @@ error[E0798]: return value of `"cmse-nonsecure-entry"` function too large to pass via registers - --> $DIR/return-via-stack.rs:25:46 + --> $DIR/return-via-stack.rs:26:46 | LL | pub extern "cmse-nonsecure-entry" fn f1() -> ReprCU64 { | ^^^^^^^^ this type doesn't fit in the available registers @@ -8,7 +8,7 @@ LL | pub extern "cmse-nonsecure-entry" fn f1() -> ReprCU64 { = 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:30:46 + --> $DIR/return-via-stack.rs:31:46 | LL | pub extern "cmse-nonsecure-entry" fn f2() -> ReprCBytes { | ^^^^^^^^^^ this type doesn't fit in the available registers @@ -17,7 +17,7 @@ LL | pub extern "cmse-nonsecure-entry" fn f2() -> ReprCBytes { = 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:35:46 + --> $DIR/return-via-stack.rs:36:46 | LL | pub extern "cmse-nonsecure-entry" fn f3() -> U64Compound { | ^^^^^^^^^^^ this type doesn't fit in the available registers @@ -26,7 +26,7 @@ LL | pub extern "cmse-nonsecure-entry" fn f3() -> U64Compound { = 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:40:46 + --> $DIR/return-via-stack.rs:41:46 | LL | pub extern "cmse-nonsecure-entry" fn f4() -> ReprCAlign16 { | ^^^^^^^^^^^^ this type doesn't fit in the available registers @@ -35,7 +35,7 @@ LL | pub extern "cmse-nonsecure-entry" fn f4() -> ReprCAlign16 { = 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:47:46 + --> $DIR/return-via-stack.rs:48:46 | LL | pub extern "cmse-nonsecure-entry" fn f5() -> [u8; 5] { | ^^^^^^^ this type doesn't fit in the available registers @@ -44,7 +44,7 @@ LL | pub extern "cmse-nonsecure-entry" fn f5() -> [u8; 5] { = 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:53:48 + --> $DIR/return-via-stack.rs:54:48 | LL | pub extern "cmse-nonsecure-entry" fn u128() -> u128 { | ^^^^ this type doesn't fit in the available registers @@ -53,7 +53,7 @@ LL | pub extern "cmse-nonsecure-entry" fn u128() -> u128 { = 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:59:48 + --> $DIR/return-via-stack.rs:60:48 | LL | pub extern "cmse-nonsecure-entry" fn i128() -> i128 { | ^^^^ this type doesn't fit in the available registers @@ -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`.