Skip to content

Commit a0131f0

Browse files
committed
change might_permit_raw_init to fully detect LLVM UB, but not more than that
1 parent 02cd79a commit a0131f0

File tree

10 files changed

+393
-240
lines changed

10 files changed

+393
-240
lines changed

compiler/rustc_const_eval/src/lib.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ extern crate rustc_middle;
3232
pub mod const_eval;
3333
mod errors;
3434
pub mod interpret;
35-
mod might_permit_raw_init;
3635
pub mod transform;
3736
pub mod util;
3837

@@ -61,7 +60,6 @@ pub fn provide(providers: &mut Providers) {
6160
const_eval::deref_mir_constant(tcx, param_env, value)
6261
};
6362
providers.permits_uninit_init =
64-
|tcx, ty| might_permit_raw_init::might_permit_raw_init(tcx, ty, InitKind::Uninit);
65-
providers.permits_zero_init =
66-
|tcx, ty| might_permit_raw_init::might_permit_raw_init(tcx, ty, InitKind::Zero);
63+
|tcx, ty| util::might_permit_raw_init(tcx, ty, InitKind::UninitMitigated0x01Fill);
64+
providers.permits_zero_init = |tcx, ty| util::might_permit_raw_init(tcx, ty, InitKind::Zero);
6765
}

compiler/rustc_const_eval/src/might_permit_raw_init.rs

-44
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout};
2+
use rustc_middle::ty::{ParamEnv, TyCtxt};
3+
use rustc_session::Limit;
4+
use rustc_target::abi::{Abi, FieldsShape, InitKind, Scalar, Variants};
5+
6+
use crate::const_eval::CompileTimeInterpreter;
7+
use crate::interpret::{InterpCx, MemoryKind, OpTy};
8+
9+
/// Determines if this type permits "raw" initialization by just transmuting some memory into an
10+
/// instance of `T`.
11+
///
12+
/// `init_kind` indicates if the memory is zero-initialized or left uninitialized. We assume
13+
/// uninitialized memory is mitigated by filling it with 0x01, which reduces the chance of causing
14+
/// LLVM UB.
15+
///
16+
/// By default we check whether that operation would cause *LLVM UB*, i.e., whether the LLVM IR we
17+
/// generate has UB or not. This is a mitigation strategy, which is why we are okay with accepting
18+
/// Rust UB as long as there is no risk of miscompilations. The `strict_init_checks` can be set to
19+
/// do a full check against Rust UB instead (in which case we will also ignore the 0x01-filling and
20+
/// to the full uninit check).
21+
pub fn might_permit_raw_init<'tcx>(
22+
tcx: TyCtxt<'tcx>,
23+
ty: TyAndLayout<'tcx>,
24+
kind: InitKind,
25+
) -> bool {
26+
if tcx.sess.opts.unstable_opts.strict_init_checks {
27+
might_permit_raw_init_strict(ty, tcx, kind)
28+
} else {
29+
let layout_cx = LayoutCx { tcx, param_env: ParamEnv::reveal_all() };
30+
might_permit_raw_init_lax(ty, &layout_cx, kind)
31+
}
32+
}
33+
34+
/// Implements the 'strict' version of the `might_permit_raw_init` checks; see that function for
35+
/// details.
36+
fn might_permit_raw_init_strict<'tcx>(
37+
ty: TyAndLayout<'tcx>,
38+
tcx: TyCtxt<'tcx>,
39+
kind: InitKind,
40+
) -> bool {
41+
let machine = CompileTimeInterpreter::new(
42+
Limit::new(0),
43+
/*can_access_statics:*/ false,
44+
/*check_alignment:*/ true,
45+
);
46+
47+
let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine);
48+
49+
let allocated = cx
50+
.allocate(ty, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap))
51+
.expect("OOM: failed to allocate for uninit check");
52+
53+
if kind == InitKind::Zero {
54+
cx.write_bytes_ptr(
55+
allocated.ptr,
56+
std::iter::repeat(0_u8).take(ty.layout.size().bytes_usize()),
57+
)
58+
.expect("failed to write bytes for zero valid check");
59+
}
60+
61+
let ot: OpTy<'_, _> = allocated.into();
62+
63+
// Assume that if it failed, it's a validation failure.
64+
// This does *not* actually check that references are dereferenceable, but since all types that
65+
// require dereferenceability also require non-null, we don't actually get any false negatives
66+
// due to this.
67+
cx.validate_operand(&ot).is_ok()
68+
}
69+
70+
/// Implements the 'lax' (default) version of the `might_permit_raw_init` checks; see that function for
71+
/// details.
72+
fn might_permit_raw_init_lax<'tcx>(
73+
this: TyAndLayout<'tcx>,
74+
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>,
75+
init_kind: InitKind,
76+
) -> bool {
77+
let scalar_allows_raw_init = move |s: Scalar| -> bool {
78+
match init_kind {
79+
InitKind::Zero => {
80+
// The range must contain 0.
81+
s.valid_range(cx).contains(0)
82+
}
83+
InitKind::UninitMitigated0x01Fill => {
84+
// The range must include an 0x01-filled buffer.
85+
let mut val: u128 = 0x01;
86+
for _ in 1..s.size(cx).bytes() {
87+
// For sizes >1, repeat the 0x01.
88+
val = (val << 8) | 0x01;
89+
}
90+
s.valid_range(cx).contains(val)
91+
}
92+
}
93+
};
94+
95+
// Check the ABI.
96+
let valid = match this.abi {
97+
Abi::Uninhabited => false, // definitely UB
98+
Abi::Scalar(s) => scalar_allows_raw_init(s),
99+
Abi::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2),
100+
Abi::Vector { element: s, count } => count == 0 || scalar_allows_raw_init(s),
101+
Abi::Aggregate { .. } => true, // Fields are checked below.
102+
};
103+
if !valid {
104+
// This is definitely not okay.
105+
return false;
106+
}
107+
108+
// Special magic check for references and boxes (i.e., special pointer types).
109+
if let Some(pointee) = this.ty.builtin_deref(false) {
110+
let pointee = cx.layout_of(pointee.ty).expect("need to be able to compute layouts");
111+
// We need to ensure that the LLVM attributes `aligned` and `dereferenceable(size)` are satisfied.
112+
if pointee.align.abi.bytes() > 1 {
113+
// 0x01-filling is not aligned.
114+
return false;
115+
}
116+
if pointee.size.bytes() > 0 {
117+
// A 'fake' integer pointer is not sufficiently dereferenceable.
118+
return false;
119+
}
120+
}
121+
122+
// If we have not found an error yet, we need to recursively descend into fields.
123+
match &this.fields {
124+
FieldsShape::Primitive | FieldsShape::Union { .. } => {}
125+
FieldsShape::Array { .. } => {
126+
// Arrays never have scalar layout in LLVM, so if the array is not actually
127+
// accessed, there is no LLVM UB -- therefore we can skip this.
128+
}
129+
FieldsShape::Arbitrary { offsets, .. } => {
130+
for idx in 0..offsets.len() {
131+
if !might_permit_raw_init_lax(this.field(cx, idx), cx, init_kind) {
132+
// We found a field that is unhappy with this kind of initialization.
133+
return false;
134+
}
135+
}
136+
}
137+
}
138+
139+
match &this.variants {
140+
Variants::Single { .. } => {
141+
// All fields of this single variant have already been checked above, there is nothing
142+
// else to do.
143+
}
144+
Variants::Multiple { .. } => {
145+
// We cannot tell LLVM anything about the details of this multi-variant layout, so
146+
// invalid values "hidden" inside the variant cannot cause LLVM trouble.
147+
}
148+
}
149+
150+
true
151+
}

compiler/rustc_const_eval/src/util/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ mod alignment;
33
mod call_kind;
44
pub mod collect_writes;
55
mod find_self_call;
6+
mod might_permit_raw_init;
67

78
pub use self::aggregate::expand_aggregate;
89
pub use self::alignment::is_disaligned;
910
pub use self::call_kind::{call_kind, CallDesugaringKind, CallKind};
1011
pub use self::find_self_call::find_self_call;
12+
pub use self::might_permit_raw_init::might_permit_raw_init;

compiler/rustc_target/src/abi/mod.rs

+1-69
Original file line numberDiff line numberDiff line change
@@ -1392,7 +1392,7 @@ pub struct PointeeInfo {
13921392
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13931393
pub enum InitKind {
13941394
Zero,
1395-
Uninit,
1395+
UninitMitigated0x01Fill,
13961396
}
13971397

13981398
/// Trait that needs to be implemented by the higher-level type representation
@@ -1498,72 +1498,4 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {
14981498
Abi::Aggregate { sized } => sized && self.size.bytes() == 0,
14991499
}
15001500
}
1501-
1502-
/// Determines if this type permits "raw" initialization by just transmuting some
1503-
/// memory into an instance of `T`.
1504-
///
1505-
/// `init_kind` indicates if the memory is zero-initialized or left uninitialized.
1506-
///
1507-
/// This code is intentionally conservative, and will not detect
1508-
/// * zero init of an enum whose 0 variant does not allow zero initialization
1509-
/// * making uninitialized types who have a full valid range (ints, floats, raw pointers)
1510-
/// * Any form of invalid value being made inside an array (unless the value is uninhabited)
1511-
///
1512-
/// A strict form of these checks that uses const evaluation exists in
1513-
/// `rustc_const_eval::might_permit_raw_init`, and a tracking issue for making these checks
1514-
/// stricter is <https://github.com/rust-lang/rust/issues/66151>.
1515-
///
1516-
/// FIXME: Once all the conservatism is removed from here, and the checks are ran by default,
1517-
/// we can use the const evaluation checks always instead.
1518-
pub fn might_permit_raw_init<C>(self, cx: &C, init_kind: InitKind) -> bool
1519-
where
1520-
Self: Copy,
1521-
Ty: TyAbiInterface<'a, C>,
1522-
C: HasDataLayout,
1523-
{
1524-
let scalar_allows_raw_init = move |s: Scalar| -> bool {
1525-
match init_kind {
1526-
InitKind::Zero => {
1527-
// The range must contain 0.
1528-
s.valid_range(cx).contains(0)
1529-
}
1530-
InitKind::Uninit => {
1531-
// The range must include all values.
1532-
s.is_always_valid(cx)
1533-
}
1534-
}
1535-
};
1536-
1537-
// Check the ABI.
1538-
let valid = match self.abi {
1539-
Abi::Uninhabited => false, // definitely UB
1540-
Abi::Scalar(s) => scalar_allows_raw_init(s),
1541-
Abi::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2),
1542-
Abi::Vector { element: s, count } => count == 0 || scalar_allows_raw_init(s),
1543-
Abi::Aggregate { .. } => true, // Fields are checked below.
1544-
};
1545-
if !valid {
1546-
// This is definitely not okay.
1547-
return false;
1548-
}
1549-
1550-
// If we have not found an error yet, we need to recursively descend into fields.
1551-
match &self.fields {
1552-
FieldsShape::Primitive | FieldsShape::Union { .. } => {}
1553-
FieldsShape::Array { .. } => {
1554-
// FIXME(#66151): For now, we are conservative and do not check arrays by default.
1555-
}
1556-
FieldsShape::Arbitrary { offsets, .. } => {
1557-
for idx in 0..offsets.len() {
1558-
if !self.field(cx, idx).might_permit_raw_init(cx, init_kind) {
1559-
// We found a field that is unhappy with this kind of initialization.
1560-
return false;
1561-
}
1562-
}
1563-
}
1564-
}
1565-
1566-
// FIXME(#66151): For now, we are conservative and do not check `self.variants`.
1567-
true
1568-
}
15691501
}

src/test/ui/consts/assert-type-intrinsics.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ fn main() {
1111
use std::mem::MaybeUninit;
1212

1313
const _BAD1: () = unsafe {
14-
MaybeUninit::<!>::uninit().assume_init();
14+
intrinsics::assert_inhabited::<!>(); //~ERROR: any use of this value will cause an error
15+
//~^WARN: previously accepted
1516
};
1617
const _BAD2: () = {
17-
intrinsics::assert_uninit_valid::<bool>();
18+
intrinsics::assert_uninit_valid::<!>(); //~ERROR: any use of this value will cause an error
19+
//~^WARN: previously accepted
1820
};
1921
const _BAD3: () = {
20-
intrinsics::assert_zero_valid::<&'static i32>();
22+
intrinsics::assert_zero_valid::<&'static i32>(); //~ERROR: any use of this value will cause an error
23+
//~^WARN: previously accepted
2124
};
2225
}

src/test/ui/consts/assert-type-intrinsics.stderr

+12-12
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,26 @@ error: any use of this value will cause an error
33
|
44
LL | const _BAD1: () = unsafe {
55
| ---------------
6-
LL | MaybeUninit::<!>::uninit().assume_init();
7-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
6+
LL | intrinsics::assert_inhabited::<!>();
7+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
88
|
99
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
1010
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
1111
= note: `#[deny(const_err)]` on by default
1212

1313
error: any use of this value will cause an error
14-
--> $DIR/assert-type-intrinsics.rs:17:9
14+
--> $DIR/assert-type-intrinsics.rs:18:9
1515
|
1616
LL | const _BAD2: () = {
1717
| ---------------
18-
LL | intrinsics::assert_uninit_valid::<bool>();
19-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to leave type `bool` uninitialized, which is invalid
18+
LL | intrinsics::assert_uninit_valid::<!>();
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
2020
|
2121
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
2222
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
2323

2424
error: any use of this value will cause an error
25-
--> $DIR/assert-type-intrinsics.rs:20:9
25+
--> $DIR/assert-type-intrinsics.rs:22:9
2626
|
2727
LL | const _BAD3: () = {
2828
| ---------------
@@ -40,29 +40,29 @@ error: any use of this value will cause an error
4040
|
4141
LL | const _BAD1: () = unsafe {
4242
| ---------------
43-
LL | MaybeUninit::<!>::uninit().assume_init();
44-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
43+
LL | intrinsics::assert_inhabited::<!>();
44+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
4545
|
4646
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
4747
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
4848
= note: `#[deny(const_err)]` on by default
4949

5050
Future breakage diagnostic:
5151
error: any use of this value will cause an error
52-
--> $DIR/assert-type-intrinsics.rs:17:9
52+
--> $DIR/assert-type-intrinsics.rs:18:9
5353
|
5454
LL | const _BAD2: () = {
5555
| ---------------
56-
LL | intrinsics::assert_uninit_valid::<bool>();
57-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to leave type `bool` uninitialized, which is invalid
56+
LL | intrinsics::assert_uninit_valid::<!>();
57+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
5858
|
5959
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
6060
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
6161
= note: `#[deny(const_err)]` on by default
6262

6363
Future breakage diagnostic:
6464
error: any use of this value will cause an error
65-
--> $DIR/assert-type-intrinsics.rs:20:9
65+
--> $DIR/assert-type-intrinsics.rs:22:9
6666
|
6767
LL | const _BAD3: () = {
6868
| ---------------

0 commit comments

Comments
 (0)