Skip to content

Commit 4c29735

Browse files
Rollup merge of rust-lang#157397 - folkertdev:cmse-clear-padding, r=davidtwco,RalfJung
cmse: clear padding when crossing the secure boundary tracking issue: rust-lang#81391 tracking issue: rust-lang#75835 RFC: rust-lang/rfcs#3884 related: rust-lang#147697 quick context: cmse creates a distinction between code running in secure mode and non-secure mode (think kernel space versus user space). Secure mode has access to data (e.g. encryption keys) that must not leak to non-secure mode. They use a special calling convention that clears unused registers, but padding in arguments/return values can contain stale secure data. This PR clears the padding bytes (and similar, e.g. space not used in any variant of a union/enum) when values are passed over the secure boundary. Separately we'll have a lint to warn on enums and unions being passed across the boundary: for them we can't statically know whether the variant that is passed contains padding. This is conceptually modeled after a similar feature in `clang` ([implementation](https://github.com/llvm/llvm-project/blob/065a39b9f7f06fca0926394096ee1c1fac41d446/clang/lib/CodeGen/CGCall.cpp#L4041-L4087)). cc @Jules-Bertholet r? @davidtwco
2 parents 74d1cd1 + 5f34c50 commit 4c29735

7 files changed

Lines changed: 426 additions & 55 deletions

File tree

compiler/rustc_abi/src/layout/ty.rs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use std::fmt;
2-
use std::ops::Deref;
2+
use std::ops::{Deref, Range};
33

44
use rustc_data_structures::intern::Interned;
5+
use rustc_data_structures::range_set::RangeSet;
56
use rustc_macros::StableHash;
67

78
use crate::layout::{FieldIdx, VariantIdx};
@@ -282,4 +283,89 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {
282283
}
283284
found
284285
}
286+
287+
/// The ranges of bytes that are always ignored by the representation relation of this type.
288+
///
289+
/// In other words, for any sequence of bytes, if we reset the these padding bytes to uninit,
290+
/// then these two sequences of bytes represent the same value (or they are both invalid).
291+
/// This is the "guaranteed" padding. There may be more bytes that are padding for some
292+
/// but not all variants of this type; those are not included.
293+
/// (E.g. `Option<i8>` has no guaranteed padding so the empty range set is returned, but its `None` value still has padding).
294+
pub fn padding_ranges<C>(&self, cx: &C) -> Vec<Range<Size>>
295+
where
296+
Ty: TyAbiInterface<'a, C> + Copy,
297+
{
298+
let mut data = RangeSet::new();
299+
self.add_data_ranges(cx, Size::ZERO, &mut data);
300+
301+
// Find gaps between the data ranges.
302+
let mut uninit_ranges = Vec::new();
303+
let mut covered_until = Size::ZERO;
304+
for &(offset, size) in data.0.iter() {
305+
if offset > covered_until {
306+
uninit_ranges.push(covered_until..offset);
307+
}
308+
covered_until = Ord::max(covered_until, offset + size);
309+
}
310+
311+
// Add trailing padding.
312+
if self.size > covered_until {
313+
uninit_ranges.push(covered_until..self.size);
314+
}
315+
316+
uninit_ranges
317+
}
318+
319+
/// Extend `out` with all ranges of bytes that *may* carry relevant data for values of this type.
320+
/// For enums and unions there are offsets that are initialized for some
321+
/// variants but not for others; those offset *will* get added to `out`.
322+
fn add_data_ranges<C>(self, cx: &C, base_offset: Size, out: &mut RangeSet<Size>)
323+
where
324+
Ty: TyAbiInterface<'a, C> + Copy,
325+
{
326+
if self.is_zst() {
327+
return;
328+
}
329+
330+
match &self.variants {
331+
Variants::Empty => { /* done */ }
332+
Variants::Single { index: _ } => match &self.fields {
333+
FieldsShape::Primitive => {
334+
out.add_range(base_offset, self.size);
335+
}
336+
&FieldsShape::Union(field_count) => {
337+
for field in 0..field_count.get() {
338+
let field = self.field(cx, field);
339+
field.add_data_ranges(cx, base_offset, out);
340+
}
341+
}
342+
&FieldsShape::Array { stride, count } => {
343+
let elem = self.field(cx, 0);
344+
345+
// For scalars we know there is no padding between the elements,
346+
// so the entire array is a single big data range.
347+
if elem.backend_repr.is_scalar() {
348+
out.add_range(base_offset, elem.size * count);
349+
} else {
350+
// FIXME: this is really inefficient for large arrays.
351+
for idx in 0..count {
352+
elem.add_data_ranges(cx, base_offset + idx * stride, out);
353+
}
354+
}
355+
}
356+
FieldsShape::Arbitrary { offsets, in_memory_order: _ } => {
357+
for (field, &offset) in offsets.iter_enumerated() {
358+
let field = self.field(cx, field.as_usize());
359+
field.add_data_ranges(cx, base_offset + offset, out);
360+
}
361+
}
362+
},
363+
Variants::Multiple { variants, .. } => {
364+
for variant in variants.indices() {
365+
let variant = self.for_variant(cx, variant);
366+
variant.add_data_ranges(cx, base_offset, out);
367+
}
368+
}
369+
}
370+
}
285371
}

compiler/rustc_abi/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,7 @@ impl FromStr for Endian {
792792
}
793793

794794
/// Size of a type in bytes.
795-
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
795+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
796796
#[cfg_attr(feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, StableHash))]
797797
pub struct Size {
798798
raw: u64,

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::cmp;
2+
use std::ops::Range;
23

3-
use rustc_abi::{Align, BackendRepr, ExternAbi, HasDataLayout, Reg, Size, WrappingRange};
4+
use rustc_abi::{
5+
Align, ArmCall, BackendRepr, CanonAbi, ExternAbi, HasDataLayout, Reg, Size, WrappingRange,
6+
};
47
use rustc_ast as ast;
58
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
69
use rustc_data_structures::packed::Pu128;
@@ -597,6 +600,20 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
597600
}
598601
ZeroSized => bug!("ZST return value shouldn't be in PassMode::Cast"),
599602
};
603+
604+
if self.fn_abi.conv == CanonAbi::Arm(ArmCall::CCmseNonSecureEntry) {
605+
// The return value of an `extern "cmse-nonsecure-entry"` function crosses the
606+
// secure boundary. Zero padding bytes so information does not leak.
607+
//
608+
// This only zeroes "guaranteed" padding. There may be more bytes that are
609+
// padding for some but not all variants of this type; those are not zeroed.
610+
//
611+
// Returning a value with value-dependent padding will instead trigger a lint.
612+
let ret_layout = self.fn_abi.ret.layout;
613+
let uninit_ranges = ret_layout.padding_ranges(bx.cx());
614+
self.zero_byte_ranges(bx, llslot, ret_layout.size, &uninit_ranges);
615+
}
616+
600617
load_cast(bx, cast_ty, llslot, self.fn_abi.ret.layout.align.abi)
601618
}
602619
};
@@ -1341,6 +1358,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
13411358

13421359
self.codegen_argument(
13431360
bx,
1361+
fn_abi.conv,
13441362
op,
13451363
by_move,
13461364
&mut llargs,
@@ -1351,6 +1369,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
13511369
let num_untupled = untuple.map(|tup| {
13521370
self.codegen_arguments_untupled(
13531371
bx,
1372+
fn_abi.conv,
13541373
&tup.node,
13551374
&mut llargs,
13561375
&fn_abi.args[first_args.len()..],
@@ -1380,6 +1399,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
13801399
let last_arg = fn_abi.args.last().unwrap();
13811400
self.codegen_argument(
13821401
bx,
1402+
fn_abi.conv,
13831403
location,
13841404
/* by_move */ false,
13851405
&mut llargs,
@@ -1696,9 +1716,31 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
16961716
}
16971717
}
16981718

1719+
fn zero_byte_ranges(
1720+
&mut self,
1721+
bx: &mut Bx,
1722+
ptr: Bx::Value,
1723+
limit: Size,
1724+
ranges: &[Range<Size>],
1725+
) {
1726+
let zero = bx.const_u8(0);
1727+
1728+
for range in ranges {
1729+
let end = cmp::min(range.end, limit);
1730+
if range.start >= end {
1731+
continue;
1732+
}
1733+
let offset = bx.const_usize(range.start.bytes());
1734+
let len = bx.const_usize((end - range.start).bytes());
1735+
let ptr = bx.inbounds_ptradd(ptr, offset);
1736+
bx.memset(ptr, zero, len, Align::ONE, MemFlags::empty());
1737+
}
1738+
}
1739+
16991740
fn codegen_argument(
17001741
&mut self,
17011742
bx: &mut Bx,
1743+
conv: CanonAbi,
17021744
op: OperandRef<'tcx, Bx::Value>,
17031745
by_move: bool,
17041746
llargs: &mut Vec<Bx::Value>,
@@ -1822,6 +1864,23 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
18221864
MemFlags::empty(),
18231865
None,
18241866
);
1867+
1868+
// The arguments of an `extern "cmse-nonsecure-call"` function cross the secure
1869+
// boundary. Zero padding bytes so information does not leak.
1870+
//
1871+
// This only zeroes "guaranteed" padding. There may be more bytes that are
1872+
// padding for some but not all variants of this type; those are not zeroed.
1873+
//
1874+
// Passing an argument with value-dependent padding will instead trigger a lint.
1875+
if conv == CanonAbi::Arm(ArmCall::CCmseNonSecureCall) {
1876+
self.zero_byte_ranges(
1877+
bx,
1878+
llscratch,
1879+
Size::from_bytes(copy_bytes),
1880+
&arg.layout.padding_ranges(bx.cx()),
1881+
);
1882+
}
1883+
18251884
// ...and then load it with the ABI type.
18261885
llval = load_cast(bx, cast, llscratch, scratch_align);
18271886
bx.lifetime_end(llscratch, scratch_size);
@@ -1848,6 +1907,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
18481907
fn codegen_arguments_untupled(
18491908
&mut self,
18501909
bx: &mut Bx,
1910+
conv: CanonAbi,
18511911
operand: &mir::Operand<'tcx>,
18521912
llargs: &mut Vec<Bx::Value>,
18531913
args: &[ArgAbi<'tcx, Ty<'tcx>>],
@@ -1867,6 +1927,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
18671927
let field = bx.load_operand(field_ptr);
18681928
self.codegen_argument(
18691929
bx,
1930+
conv,
18701931
field,
18711932
by_move,
18721933
llargs,
@@ -1878,7 +1939,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
18781939
// If the tuple is immediate, the elements are as well.
18791940
for i in 0..tuple.layout.fields.count() {
18801941
let op = tuple.extract_field(self, bx, i);
1881-
self.codegen_argument(bx, op, by_move, llargs, &args[i], lifetime_ends_after_call);
1942+
self.codegen_argument(
1943+
bx,
1944+
conv,
1945+
op,
1946+
by_move,
1947+
llargs,
1948+
&args[i],
1949+
lifetime_ends_after_call,
1950+
);
18821951
}
18831952
}
18841953
tuple.layout.fields.count()

compiler/rustc_const_eval/src/interpret/validity.rs

Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -345,55 +345,7 @@ fn write_path(out: &mut String, path: &[PathElem<'_>]) {
345345
}
346346
}
347347

348-
/// Represents a set of `Size` values as a sorted list of ranges.
349-
// These are (offset, length) pairs, and they are sorted and mutually disjoint,
350-
// and never adjacent (i.e. there's always a gap between two of them).
351-
#[derive(Debug, Clone)]
352-
pub struct RangeSet(Vec<(Size, Size)>);
353-
354-
impl RangeSet {
355-
fn add_range(&mut self, offset: Size, size: Size) {
356-
if size.bytes() == 0 {
357-
// No need to track empty ranges.
358-
return;
359-
}
360-
let v = &mut self.0;
361-
// We scan for a partition point where the left partition is all the elements that end
362-
// strictly before we start. Those are elements that are too "low" to merge with us.
363-
let idx =
364-
v.partition_point(|&(other_offset, other_size)| other_offset + other_size < offset);
365-
// Now we want to either merge with the first element of the second partition, or insert ourselves before that.
366-
if let Some(&(other_offset, other_size)) = v.get(idx)
367-
&& offset + size >= other_offset
368-
{
369-
// Their end is >= our start (otherwise it would not be in the 2nd partition) and
370-
// our end is >= their start. This means we can merge the ranges.
371-
let new_start = other_offset.min(offset);
372-
let mut new_end = (other_offset + other_size).max(offset + size);
373-
// We grew to the right, so merge with overlapping/adjacent elements.
374-
// (We also may have grown to the left, but that can never make us adjacent with
375-
// anything there since we selected the first such candidate via `partition_point`.)
376-
let mut scan_right = 1;
377-
while let Some(&(next_offset, next_size)) = v.get(idx + scan_right)
378-
&& new_end >= next_offset
379-
{
380-
// Increase our size to absorb the next element.
381-
new_end = new_end.max(next_offset + next_size);
382-
// Look at the next element.
383-
scan_right += 1;
384-
}
385-
// Update the element we grew.
386-
v[idx] = (new_start, new_end - new_start);
387-
// Remove the elements we absorbed (if any).
388-
if scan_right > 1 {
389-
drop(v.drain((idx + 1)..(idx + scan_right)));
390-
}
391-
} else {
392-
// Insert new element.
393-
v.insert(idx, (offset, size));
394-
}
395-
}
396-
}
348+
pub type RangeSet = rustc_data_structures::range_set::RangeSet<Size>;
397349

398350
struct ValidityVisitor<'rt, 'tcx, M: Machine<'tcx>> {
399351
/// The `path` may be pushed to, but the part that is present when a function
@@ -1194,7 +1146,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
11941146
assert!(layout.is_sized(), "there are no unsized unions");
11951147
let layout_cx = LayoutCx::new(*ecx.tcx, ecx.typing_env);
11961148
return M::cached_union_data_range(ecx, layout.ty, || {
1197-
let mut out = RangeSet(Vec::new());
1149+
let mut out = RangeSet::new();
11981150
union_data_range_uncached(&layout_cx, layout, Size::ZERO, &mut out);
11991151
out
12001152
});
@@ -1644,7 +1596,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
16441596
ctfe_mode,
16451597
ecx,
16461598
reset_provenance_and_padding,
1647-
data_bytes: reset_padding.then_some(RangeSet(Vec::new())),
1599+
data_bytes: reset_padding.then_some(RangeSet::new()),
16481600
may_dangle: start_in_may_dangle,
16491601
};
16501602
v.visit_value(val)?;

compiler/rustc_data_structures/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ pub mod obligation_forest;
6868
pub mod owned_slice;
6969
pub mod packed;
7070
pub mod profiling;
71+
pub mod range_set;
7172
pub mod sharded;
7273
pub mod small_c_str;
7374
pub mod snapshot_map;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/// Represents a set of `Size` values as a sorted list of ranges.
2+
///
3+
/// These are (offset, length) pairs, and they are sorted and mutually disjoint,
4+
/// and never adjacent (i.e. there's always a gap between two of them).
5+
#[derive(Debug, Clone)]
6+
pub struct RangeSet<T>(pub Vec<(T, T)>);
7+
8+
impl<T> RangeSet<T>
9+
where
10+
T: Copy + Ord + Default,
11+
T: core::ops::Add<Output = T>,
12+
T: core::ops::Sub<Output = T>,
13+
{
14+
pub fn new() -> Self {
15+
Self(Vec::new())
16+
}
17+
18+
pub fn add_range(&mut self, offset: T, size: T) {
19+
if size == T::default() {
20+
// No need to track empty ranges.
21+
return;
22+
}
23+
let v = &mut self.0;
24+
// We scan for a partition point where the left partition is all the elements that end
25+
// strictly before we start. Those are elements that are too "low" to merge with us.
26+
let idx =
27+
v.partition_point(|&(other_offset, other_size)| other_offset + other_size < offset);
28+
// Now we want to either merge with the first element of the second partition, or insert ourselves before that.
29+
if let Some(&(other_offset, other_size)) = v.get(idx)
30+
&& offset + size >= other_offset
31+
{
32+
// Their end is >= our start (otherwise it would not be in the 2nd partition) and
33+
// our end is >= their start. This means we can merge the ranges.
34+
let new_start = other_offset.min(offset);
35+
let mut new_end = (other_offset + other_size).max(offset + size);
36+
// We grew to the right, so merge with overlapping/adjacent elements.
37+
// (We also may have grown to the left, but that can never make us adjacent with
38+
// anything there since we selected the first such candidate via `partition_point`.)
39+
let mut scan_right = 1;
40+
while let Some(&(next_offset, next_size)) = v.get(idx + scan_right)
41+
&& new_end >= next_offset
42+
{
43+
// Increase our size to absorb the next element.
44+
new_end = new_end.max(next_offset + next_size);
45+
// Look at the next element.
46+
scan_right += 1;
47+
}
48+
// Update the element we grew.
49+
v[idx] = (new_start, new_end - new_start);
50+
// Remove the elements we absorbed (if any).
51+
if scan_right > 1 {
52+
drop(v.drain((idx + 1)..(idx + scan_right)));
53+
}
54+
} else {
55+
// Insert new element.
56+
v.insert(idx, (offset, size));
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)