Skip to content

Commit 107a473

Browse files
committed
safe transmute: require that src referent is smaller than dst
The source referent absolutely must be smaller than the destination referent of a ref-to-ref transmute; the excess bytes referenced cannot arise from thin air, even if those bytes are uninitialized.
1 parent a165f1f commit 107a473

File tree

8 files changed

+122
-5
lines changed

8 files changed

+122
-5
lines changed

compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs

+7
Original file line numberDiff line numberDiff line change
@@ -3091,6 +3091,13 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
30913091
rustc_transmute::Reason::DstIsTooBig => {
30923092
format!("The size of `{src}` is smaller than the size of `{dst}`")
30933093
}
3094+
rustc_transmute::Reason::DstRefIsTooBig { src, dst } => {
3095+
let src_size = src.size;
3096+
let dst_size = dst.size;
3097+
format!(
3098+
"The referent size of `{src}` ({src_size} bytes) is smaller than that of `{dst}` ({dst_size}) bytes"
3099+
)
3100+
}
30943101
rustc_transmute::Reason::SrcSizeOverflow => {
30953102
format!(
30963103
"values of the type `{src}` are too big for the current architecture"

compiler/rustc_transmute/src/layout/mod.rs

+21
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ pub(crate) trait Def: Debug + Hash + Eq + PartialEq + Copy + Clone {
3535
pub trait Ref: Debug + Hash + Eq + PartialEq + Copy + Clone {
3636
fn min_align(&self) -> usize;
3737

38+
fn size(&self) -> usize;
39+
3840
fn is_mutable(&self) -> bool;
3941
}
4042

@@ -48,6 +50,9 @@ impl Ref for ! {
4850
fn min_align(&self) -> usize {
4951
unreachable!()
5052
}
53+
fn size(&self) -> usize {
54+
unreachable!()
55+
}
5156
fn is_mutable(&self) -> bool {
5257
unreachable!()
5358
}
@@ -57,6 +62,7 @@ impl Ref for ! {
5762
pub mod rustc {
5863
use rustc_middle::mir::Mutability;
5964
use rustc_middle::ty::{self, Ty};
65+
use std::fmt::{self, Write};
6066

6167
/// A reference in the layout.
6268
#[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone, Copy)]
@@ -65,13 +71,18 @@ pub mod rustc {
6571
pub ty: Ty<'tcx>,
6672
pub mutability: Mutability,
6773
pub align: usize,
74+
pub size: usize,
6875
}
6976

7077
impl<'tcx> super::Ref for Ref<'tcx> {
7178
fn min_align(&self) -> usize {
7279
self.align
7380
}
7481

82+
fn size(&self) -> usize {
83+
self.size
84+
}
85+
7586
fn is_mutable(&self) -> bool {
7687
match self.mutability {
7788
Mutability::Mut => true,
@@ -81,6 +92,16 @@ pub mod rustc {
8192
}
8293
impl<'tcx> Ref<'tcx> {}
8394

95+
impl<'tcx> fmt::Display for Ref<'tcx> {
96+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97+
f.write_char('&')?;
98+
if self.mutability == Mutability::Mut {
99+
f.write_str("mut ")?;
100+
}
101+
self.ty.fmt(f)
102+
}
103+
}
104+
84105
/// A visibility node in the layout.
85106
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
86107
pub enum Def<'tcx> {

compiler/rustc_transmute/src/layout/tree.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -372,12 +372,15 @@ pub(crate) mod rustc {
372372
}
373373

374374
ty::Ref(lifetime, ty, mutability) => {
375-
let align = layout_of(tcx, *ty)?.align();
375+
let layout = layout_of(tcx, *ty)?;
376+
let align = layout.align();
377+
let size = layout.size();
376378
Ok(Tree::Ref(Ref {
377379
lifetime: *lifetime,
378380
ty: *ty,
379381
mutability: *mutability,
380382
align,
383+
size,
381384
}))
382385
}
383386

compiler/rustc_transmute/src/lib.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub struct Assume {
2323
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
2424
pub enum Answer<R> {
2525
Yes,
26-
No(Reason),
26+
No(Reason<R>),
2727
If(Condition<R>),
2828
}
2929

@@ -42,7 +42,7 @@ pub enum Condition<R> {
4242

4343
/// Answers "why wasn't the source type transmutable into the destination type?"
4444
#[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone)]
45-
pub enum Reason {
45+
pub enum Reason<T> {
4646
/// The layout of the source type is unspecified.
4747
SrcIsUnspecified,
4848
/// The layout of the destination type is unspecified.
@@ -53,6 +53,13 @@ pub enum Reason {
5353
DstMayHaveSafetyInvariants,
5454
/// `Dst` is larger than `Src`, and the excess bytes were not exclusively uninitialized.
5555
DstIsTooBig,
56+
/// A referent of `Dst` is larger than a referent in `Src`.
57+
DstRefIsTooBig {
58+
/// The referent of the source type.
59+
src: T,
60+
/// The too-large referent of the destination type.
61+
dst: T,
62+
},
5663
/// Src should have a stricter alignment than Dst, but it does not.
5764
DstHasStricterAlignment { src_min_align: usize, dst_min_align: usize },
5865
/// Can't go from shared pointer to unique pointer

compiler/rustc_transmute/src/maybe_transmutable/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,11 @@ where
266266
src_min_align: src_ref.min_align(),
267267
dst_min_align: dst_ref.min_align(),
268268
})
269+
} else if dst_ref.size() > src_ref.size() {
270+
Answer::No(Reason::DstRefIsTooBig {
271+
src: src_ref,
272+
dst: src_ref,
273+
})
269274
} else {
270275
// ...such that `src` is transmutable into `dst`, if
271276
// `src_ref` is transmutability into `dst_ref`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//@ check-fail
2+
3+
//! Reject extensions behind references.
4+
5+
#![crate_type = "lib"]
6+
#![feature(transmutability)]
7+
8+
mod assert {
9+
use std::mem::{Assume, BikeshedIntrinsicFrom};
10+
11+
pub fn is_transmutable<Src, Dst>()
12+
where
13+
Dst: BikeshedIntrinsicFrom<
14+
Src,
15+
{
16+
Assume {
17+
alignment: true,
18+
lifetimes: true,
19+
safety: true,
20+
validity: true,
21+
}
22+
},
23+
>,
24+
{
25+
}
26+
}
27+
28+
#[repr(C, packed)]
29+
struct Packed<T>(T);
30+
31+
fn reject_extension() {
32+
#[repr(C, align(2))]
33+
struct Two(u8);
34+
35+
#[repr(C, align(4))]
36+
struct Four(u8);
37+
38+
// These two types differ in the number of trailing padding bytes they have.
39+
type Src = Packed<Two>;
40+
type Dst = Packed<Four>;
41+
42+
const _: () = {
43+
use std::mem::size_of;
44+
assert!(size_of::<Src>() == 2);
45+
assert!(size_of::<Dst>() == 4);
46+
};
47+
48+
assert::is_transmutable::<&Src, &Dst>(); //~ ERROR cannot be safely transmuted
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
error[E0277]: `&Packed<Two>` cannot be safely transmuted into `&Packed<Four>`
2+
--> $DIR/reject_extension.rs:48:37
3+
|
4+
LL | assert::is_transmutable::<&Src, &Dst>();
5+
| ^^^^ The referent size of `&Packed<Two>` (2 bytes) is smaller than that of `&Packed<Two>` (2) bytes
6+
|
7+
note: required by a bound in `is_transmutable`
8+
--> $DIR/reject_extension.rs:13:14
9+
|
10+
LL | pub fn is_transmutable<Src, Dst>()
11+
| --------------- required by a bound in this function
12+
LL | where
13+
LL | Dst: BikeshedIntrinsicFrom<
14+
| ______________^
15+
LL | | Src,
16+
LL | | {
17+
LL | | Assume {
18+
... |
19+
LL | | },
20+
LL | | >,
21+
| |_________^ required by this bound in `is_transmutable`
22+
23+
error: aborting due to 1 previous error
24+
25+
For more information about this error, try `rustc --explain E0277`.

tests/ui/transmutability/references/unit-to-u8.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error[E0277]: `Unit` cannot be safely transmuted into `u8`
1+
error[E0277]: `&Unit` cannot be safely transmuted into `&u8`
22
--> $DIR/unit-to-u8.rs:22:52
33
|
44
LL | assert::is_maybe_transmutable::<&'static Unit, &'static u8>();
5-
| ^^^^^^^^^^^ The size of `Unit` is smaller than the size of `u8`
5+
| ^^^^^^^^^^^ The referent size of `&Unit` (0 bytes) is smaller than that of `&Unit` (0) bytes
66
|
77
note: required by a bound in `is_maybe_transmutable`
88
--> $DIR/unit-to-u8.rs:9:14

0 commit comments

Comments
 (0)