Skip to content

Commit 5abb0ff

Browse files
committed
cmse: lint when a (partially) uninitialized value crosses the secure boundary
1 parent 226c73a commit 5abb0ff

File tree

9 files changed

+171
-127
lines changed

9 files changed

+171
-127
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4100,6 +4100,7 @@ dependencies = [
41004100
"rustc_span",
41014101
"rustc_target",
41024102
"rustc_trait_selection",
4103+
"rustc_transmute",
41034104
"smallvec",
41044105
"tracing",
41054106
"unicode-security",

compiler/rustc_lint/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ rustc_session = { path = "../rustc_session" }
2424
rustc_span = { path = "../rustc_span" }
2525
rustc_target = { path = "../rustc_target" }
2626
rustc_trait_selection = { path = "../rustc_trait_selection" }
27+
rustc_transmute = { path = "../rustc_transmute" }
2728
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
2829
tracing = "0.1"
2930
unicode-security = "0.1.0"

compiler/rustc_lint/messages.ftl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,9 @@ lint_closure_returning_async_block = closure returning async block can be made i
187187
.label = this async block can be removed, and the closure can be turned into an async closure
188188
.suggestion = turn this into an async closure
189189
190-
lint_cmse_union_may_leak_information =
191-
passing a union across the security boundary may leak information
192-
.note = the bits not used by the current variant may contain stale secure data
190+
lint_cmse_uninitialized_may_leak_information =
191+
passing a (partially) uninitialized value across the security boundary may leak information
192+
.note = padding or fields not used by the current variant of a union may contain stale secure data
193193
194194
lint_command_line_source = `forbid` lint level was set on command line
195195

compiler/rustc_lint/src/cmse_uninitialized_leak.rs

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use rustc_abi::ExternAbi;
22
use rustc_hir::{self as hir, Expr, ExprKind};
3-
use rustc_middle::ty::layout::{LayoutCx, TyAndLayout};
4-
use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt};
3+
use rustc_middle::ty::layout::TyAndLayout;
4+
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
55
use rustc_session::{declare_lint, declare_lint_pass};
66

77
use crate::{LateContext, LateLintPass, LintContext, lints};
@@ -86,11 +86,12 @@ fn check_cmse_call_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
8686
continue;
8787
};
8888

89-
if layout_contains_union(cx.tcx, &layout) {
89+
if !is_transmutable_to_array_u8(cx.tcx, ty.clone(), layout) {
90+
// Some part of the source type may be uninitialized.
9091
cx.emit_span_lint(
9192
CMSE_UNINITIALIZED_LEAK,
9293
arg.span,
93-
lints::CmseUnionMayLeakInformation,
94+
lints::CmseUninitializedMayLeakInformation,
9495
);
9596
}
9697
}
@@ -126,11 +127,12 @@ fn check_cmse_entry_return<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>)
126127
}
127128

128129
let typing_env = ty::TypingEnv::fully_monomorphized();
130+
129131
let Ok(ret_layout) = cx.tcx.layout_of(typing_env.as_query_input(return_type)) else {
130132
return;
131133
};
132134

133-
if layout_contains_union(cx.tcx, &ret_layout) {
135+
if !is_transmutable_to_array_u8(cx.tcx, return_type.clone(), ret_layout) {
134136
let return_expr_span = if is_implicit_return {
135137
match expr.kind {
136138
ExprKind::Block(block, _) => match block.expr {
@@ -143,46 +145,39 @@ fn check_cmse_entry_return<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>)
143145
expr.span
144146
};
145147

148+
// Some part of the source type may be uninitialized.
146149
cx.emit_span_lint(
147150
CMSE_UNINITIALIZED_LEAK,
148151
return_expr_span,
149-
lints::CmseUnionMayLeakInformation,
152+
lints::CmseUninitializedMayLeakInformation,
150153
);
151154
}
152155
}
153156

154-
/// Check whether any part of the layout is a union, which may contain secure data still.
155-
fn layout_contains_union<'tcx>(tcx: TyCtxt<'tcx>, layout: &TyAndLayout<'tcx>) -> bool {
156-
if layout.ty.is_union() {
157-
return true;
158-
}
159-
160-
let typing_env = ty::TypingEnv::fully_monomorphized();
161-
let cx = LayoutCx::new(tcx, typing_env);
162-
163-
match &layout.variants {
164-
rustc_abi::Variants::Single { .. } => {
165-
for i in 0..layout.fields.count() {
166-
if layout_contains_union(tcx, &layout.field(&cx, i)) {
167-
return true;
168-
}
169-
}
170-
}
171-
172-
rustc_abi::Variants::Multiple { variants, .. } => {
173-
for (variant_idx, _vdata) in variants.iter_enumerated() {
174-
let variant_layout = layout.for_variant(&cx, variant_idx);
175-
176-
for i in 0..variant_layout.fields.count() {
177-
if layout_contains_union(tcx, &variant_layout.field(&cx, i)) {
178-
return true;
179-
}
180-
}
181-
}
182-
}
183-
184-
rustc_abi::Variants::Empty => {}
157+
/// Check whether the source type `T` can be safely transmuted to `[u8; size_of::<T>()]`.
158+
///
159+
/// If the transmute is valid, `T` must be fully initialized. Otherwise, parts of it may be
160+
/// uninitialized, which should trigger the lint defined by this module.
161+
fn is_transmutable_to_array_u8<'tcx>(
162+
tcx: TyCtxt<'tcx>,
163+
ty: Ty<'tcx>,
164+
layout: TyAndLayout<'tcx>,
165+
) -> bool {
166+
use rustc_transmute::{Answer, Assume, TransmuteTypeEnv, Types};
167+
168+
let mut transmute_env = TransmuteTypeEnv::new(tcx);
169+
let assume = Assume { alignment: true, lifetimes: true, safety: false, validity: false };
170+
171+
let array_u8_ty = Ty::new_array_with_const_len(
172+
tcx,
173+
tcx.types.u8,
174+
ty::Const::from_target_usize(tcx, layout.size.bytes()),
175+
);
176+
177+
let types = Types { src: ty, dst: array_u8_ty };
178+
179+
match transmute_env.is_transmutable(types, assume) {
180+
Answer::Yes => true,
181+
Answer::No(_) | Answer::If(_) => false,
185182
}
186-
187-
false
188183
}

compiler/rustc_lint/src/lints.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,9 +1469,9 @@ pub(crate) struct NonLocalDefinitionsCargoUpdateNote {
14691469

14701470
// cmse_uninitialized_leak.rs
14711471
#[derive(LintDiagnostic)]
1472-
#[diag(lint_cmse_union_may_leak_information)]
1472+
#[diag(lint_cmse_uninitialized_may_leak_information)]
14731473
#[note]
1474-
pub(crate) struct CmseUnionMayLeakInformation;
1474+
pub(crate) struct CmseUninitializedMayLeakInformation;
14751475

14761476
// precedence.rs
14771477
#[derive(LintDiagnostic)]

tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-uninitialized.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,67 @@ extern crate minicore;
1010
use minicore::*;
1111

1212
#[repr(Rust)]
13-
pub union ReprRustUnionU64 {
13+
union ReprRustUnionU64 {
1414
_unused: u64,
1515
}
1616

17+
#[repr(Rust)]
18+
union ReprRustUnionPartiallyUninit {
19+
_unused1: u32,
20+
_unused2: u16,
21+
}
22+
1723
#[repr(C)]
18-
pub union ReprCUnionU64 {
24+
union ReprCUnionU64 {
1925
_unused: u64,
2026
_unused1: u32,
2127
}
2228

2329
#[repr(C)]
24-
pub struct ReprCAggregate {
30+
struct ReprCAggregate {
2531
a: usize,
2632
b: ReprCUnionU64,
2733
}
2834

35+
// This is an aggregate that cannot be unwrapped, and has 1 (uninitialized) padding byte.
36+
#[repr(C, align(4))]
37+
struct PaddedStruct {
38+
a: u8,
39+
b: u16,
40+
}
41+
2942
#[no_mangle]
30-
pub fn test_union(
43+
fn test_uninitialized(
3144
f1: extern "cmse-nonsecure-call" fn(ReprRustUnionU64),
3245
f2: extern "cmse-nonsecure-call" fn(ReprCUnionU64),
3346
f3: extern "cmse-nonsecure-call" fn(MaybeUninit<u32>),
3447
f4: extern "cmse-nonsecure-call" fn(MaybeUninit<u64>),
3548
f5: extern "cmse-nonsecure-call" fn((usize, MaybeUninit<u64>)),
3649
f6: extern "cmse-nonsecure-call" fn(ReprCAggregate),
50+
f7: extern "cmse-nonsecure-call" fn(ReprRustUnionPartiallyUninit),
51+
f8: extern "cmse-nonsecure-call" fn(PaddedStruct),
3752
) {
53+
// With `repr(Rust)` this union is always initialized.
3854
f1(ReprRustUnionU64 { _unused: 1 });
39-
//~^ WARN passing a union across the security boundary may leak information
4055

4156
f2(ReprCUnionU64 { _unused: 1 });
42-
//~^ WARN passing a union across the security boundary may leak information
57+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
4358

4459
f3(MaybeUninit::uninit());
45-
//~^ WARN passing a union across the security boundary may leak information
60+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
4661

4762
f4(MaybeUninit::uninit());
48-
//~^ WARN passing a union across the security boundary may leak information
63+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
4964

5065
f5((0, MaybeUninit::uninit()));
51-
//~^ WARN passing a union across the security boundary may leak information
66+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
5267

5368
f6(ReprCAggregate { a: 0, b: ReprCUnionU64 { _unused: 1 } });
54-
//~^ WARN passing a union across the security boundary may leak information
69+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
70+
71+
f7(ReprRustUnionPartiallyUninit { _unused1: 0 });
72+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
73+
74+
f8(PaddedStruct { a: 0, b: 0 });
75+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
5576
}
Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,59 @@
1-
warning: passing a union across the security boundary may leak information
2-
--> $DIR/params-uninitialized.rs:38:8
3-
|
4-
LL | f1(ReprRustUnionU64 { _unused: 1 });
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6-
|
7-
= note: the bits not used by the current variant may contain stale secure data
8-
= note: `#[warn(cmse_uninitialized_leak)]` on by default
9-
10-
warning: passing a union across the security boundary may leak information
11-
--> $DIR/params-uninitialized.rs:41:8
1+
warning: passing a (partially) uninitialized value across the security boundary may leak information
2+
--> $DIR/params-uninitialized.rs:56:8
123
|
134
LL | f2(ReprCUnionU64 { _unused: 1 });
145
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
156
|
16-
= note: the bits not used by the current variant may contain stale secure data
7+
= note: padding or fields not used by the current variant of a union may contain stale secure data
8+
= note: `#[warn(cmse_uninitialized_leak)]` on by default
179

18-
warning: passing a union across the security boundary may leak information
19-
--> $DIR/params-uninitialized.rs:44:8
10+
warning: passing a (partially) uninitialized value across the security boundary may leak information
11+
--> $DIR/params-uninitialized.rs:59:8
2012
|
2113
LL | f3(MaybeUninit::uninit());
2214
| ^^^^^^^^^^^^^^^^^^^^^
2315
|
24-
= note: the bits not used by the current variant may contain stale secure data
16+
= note: padding or fields not used by the current variant of a union may contain stale secure data
2517

26-
warning: passing a union across the security boundary may leak information
27-
--> $DIR/params-uninitialized.rs:47:8
18+
warning: passing a (partially) uninitialized value across the security boundary may leak information
19+
--> $DIR/params-uninitialized.rs:62:8
2820
|
2921
LL | f4(MaybeUninit::uninit());
3022
| ^^^^^^^^^^^^^^^^^^^^^
3123
|
32-
= note: the bits not used by the current variant may contain stale secure data
24+
= note: padding or fields not used by the current variant of a union may contain stale secure data
3325

34-
warning: passing a union across the security boundary may leak information
35-
--> $DIR/params-uninitialized.rs:50:8
26+
warning: passing a (partially) uninitialized value across the security boundary may leak information
27+
--> $DIR/params-uninitialized.rs:65:8
3628
|
3729
LL | f5((0, MaybeUninit::uninit()));
3830
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
3931
|
40-
= note: the bits not used by the current variant may contain stale secure data
32+
= note: padding or fields not used by the current variant of a union may contain stale secure data
4133

42-
warning: passing a union across the security boundary may leak information
43-
--> $DIR/params-uninitialized.rs:53:8
34+
warning: passing a (partially) uninitialized value across the security boundary may leak information
35+
--> $DIR/params-uninitialized.rs:68:8
4436
|
4537
LL | f6(ReprCAggregate { a: 0, b: ReprCUnionU64 { _unused: 1 } });
4638
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4739
|
48-
= note: the bits not used by the current variant may contain stale secure data
40+
= note: padding or fields not used by the current variant of a union may contain stale secure data
41+
42+
warning: passing a (partially) uninitialized value across the security boundary may leak information
43+
--> $DIR/params-uninitialized.rs:71:8
44+
|
45+
LL | f7(ReprRustUnionPartiallyUninit { _unused1: 0 });
46+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
47+
|
48+
= note: padding or fields not used by the current variant of a union may contain stale secure data
49+
50+
warning: passing a (partially) uninitialized value across the security boundary may leak information
51+
--> $DIR/params-uninitialized.rs:74:8
52+
|
53+
LL | f8(PaddedStruct { a: 0, b: 0 });
54+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
55+
|
56+
= note: padding or fields not used by the current variant of a union may contain stale secure data
4957

50-
warning: 6 warnings emitted
58+
warning: 7 warnings emitted
5159

tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-uninitialized.rs

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,42 +10,67 @@ extern crate minicore;
1010
use minicore::*;
1111

1212
#[repr(Rust)]
13-
pub union ReprRustUnionU64 {
13+
union ReprRustUnionU64 {
1414
_unused: u64,
1515
}
1616

1717
#[no_mangle]
1818
#[allow(improper_ctypes_definitions)]
19-
pub extern "cmse-nonsecure-entry" fn union_rust() -> ReprRustUnionU64 {
19+
extern "cmse-nonsecure-entry" fn union_rust() -> ReprRustUnionU64 {
20+
// With `repr(Rust)` value is always fully initialized.
2021
ReprRustUnionU64 { _unused: 1 }
21-
//~^ WARN passing a union across the security boundary may leak information
22+
}
23+
24+
#[repr(Rust)]
25+
union ReprRustUnionPartiallyUninit {
26+
_unused1: u32,
27+
_unused2: u16,
28+
}
29+
30+
#[no_mangle]
31+
#[allow(improper_ctypes_definitions)]
32+
extern "cmse-nonsecure-entry" fn union_rust_partially_uninit() -> ReprRustUnionPartiallyUninit {
33+
ReprRustUnionPartiallyUninit { _unused1: 1 }
34+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
2235
}
2336

2437
#[no_mangle]
25-
pub extern "cmse-nonsecure-entry" fn maybe_uninit_32bit() -> MaybeUninit<u32> {
38+
extern "cmse-nonsecure-entry" fn maybe_uninit_32bit() -> MaybeUninit<u32> {
2639
MaybeUninit::uninit()
27-
//~^ WARN passing a union across the security boundary may leak information
40+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
2841
}
2942

3043
#[no_mangle]
31-
pub extern "cmse-nonsecure-entry" fn maybe_uninit_64bit() -> MaybeUninit<f64> {
44+
extern "cmse-nonsecure-entry" fn maybe_uninit_64bit() -> MaybeUninit<f64> {
3245
if true {
3346
return MaybeUninit::new(6.28);
34-
//~^ WARN passing a union across the security boundary may leak information
47+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
3548
}
3649
MaybeUninit::new(3.14)
37-
//~^ WARN passing a union across the security boundary may leak information
50+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
3851
}
3952

4053
#[repr(transparent)]
41-
pub struct Wrapper(ReprRustUnionU64);
54+
struct Wrapper(MaybeUninit<u64>);
4255

4356
#[no_mangle]
44-
pub extern "cmse-nonsecure-entry" fn repr_transparent_union() -> Wrapper {
45-
//~^ WARN improper_ctypes_definitions
57+
extern "cmse-nonsecure-entry" fn repr_transparent_union() -> Wrapper {
4658
match 0 {
47-
//~^ WARN passing a union across the security boundary may leak information
48-
0 => Wrapper(ReprRustUnionU64 { _unused: 1 }),
49-
_ => Wrapper(ReprRustUnionU64 { _unused: 2 }),
59+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
60+
0 => Wrapper(MaybeUninit::new(0)),
61+
_ => Wrapper(MaybeUninit::new(1)),
5062
}
5163
}
64+
65+
// This is an aggregate that cannot be unwrapped, and has 1 (uninitialized) padding byte.
66+
#[repr(C, align(4))]
67+
struct PaddedStruct {
68+
a: u8,
69+
b: u16,
70+
}
71+
72+
#[no_mangle]
73+
extern "cmse-nonsecure-entry" fn padded_struct() -> PaddedStruct {
74+
PaddedStruct { a: 0, b: 1 }
75+
//~^ WARN passing a (partially) uninitialized value across the security boundary may leak information
76+
}

0 commit comments

Comments
 (0)