Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit db44cae

Browse files
committedJun 11, 2024
interpret: ensure we check bool/char for validity when they are used in a cast
1 parent aec67e2 commit db44cae

File tree

10 files changed

+159
-36
lines changed

10 files changed

+159
-36
lines changed
 

‎compiler/rustc_const_eval/src/interpret/cast.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
274274
// Let's make sure v is sign-extended *if* it has a signed type.
275275
let signed = src_layout.abi.is_signed(); // Also asserts that abi is `Scalar`.
276276

277-
let v = scalar.to_bits(src_layout.size)?;
278-
let v = if signed { self.sign_extend(v, src_layout) } else { v };
279-
trace!("cast_from_scalar: {}, {} -> {}", v, src_layout.ty, cast_ty);
277+
let v = match src_layout.ty.kind() {
278+
Uint(_) | RawPtr(..) | FnPtr(..) => scalar.to_uint(src_layout.size)?,
279+
Int(_) => scalar.to_int(src_layout.size)? as u128, // we will cast back to `i128` below if the sign matters
280+
Bool => scalar.to_bool()?.into(),
281+
Char => scalar.to_char()?.into(),
282+
_ => span_bug!(self.cur_span(), "invalid int-like cast from {}", src_layout.ty),
283+
};
280284

281285
Ok(match *cast_ty.kind() {
282286
// int -> int

‎compiler/rustc_const_eval/src/interpret/intrinsics.rs

+7-9
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
197197
// rotate_right: (X << ((BW - S) % BW)) | (X >> (S % BW))
198198
let layout_val = self.layout_of(instance_args.type_at(0))?;
199199
let val = self.read_scalar(&args[0])?;
200-
let val_bits = val.to_bits(layout_val.size)?;
200+
let val_bits = val.to_bits(layout_val.size)?; // sign is ignored here
201201

202202
let layout_raw_shift = self.layout_of(self.tcx.types.u32)?;
203203
let raw_shift = self.read_scalar(&args[1])?;
@@ -484,7 +484,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
484484
ret_layout: TyAndLayout<'tcx>,
485485
) -> InterpResult<'tcx, Scalar<M::Provenance>> {
486486
assert!(layout.ty.is_integral(), "invalid type for numeric intrinsic: {}", layout.ty);
487-
let bits = val.to_bits(layout.size)?;
487+
let bits = val.to_bits(layout.size)?; // these operations all ignore the sign
488488
let extra = 128 - u128::from(layout.size.bits());
489489
let bits_out = match name {
490490
sym::ctpop => u128::from(bits.count_ones()),
@@ -519,6 +519,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
519519
// `x % y != 0` or `y == 0` or `x == T::MIN && y == -1`.
520520
// First, check x % y != 0 (or if that computation overflows).
521521
let rem = self.binary_op(BinOp::Rem, a, b)?;
522+
// sign does not matter for 0 test, so `to_bits` is fine
522523
if rem.to_scalar().to_bits(a.layout.size)? != 0 {
523524
throw_ub_custom!(
524525
fluent::const_eval_exact_div_has_remainder,
@@ -545,22 +546,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
545546
self.binary_op(mir_op.wrapping_to_overflowing().unwrap(), l, r)?.to_scalar_pair();
546547
Ok(if overflowed.to_bool()? {
547548
let size = l.layout.size;
548-
let num_bits = size.bits();
549549
if l.layout.abi.is_signed() {
550550
// For signed ints the saturated value depends on the sign of the first
551551
// term since the sign of the second term can be inferred from this and
552552
// the fact that the operation has overflowed (if either is 0 no
553553
// overflow can occur)
554-
let first_term: u128 = l.to_scalar().to_bits(l.layout.size)?;
555-
let first_term_positive = first_term & (1 << (num_bits - 1)) == 0;
556-
if first_term_positive {
554+
let first_term: i128 = l.to_scalar().to_int(l.layout.size)?;
555+
if first_term >= 0 {
557556
// Negative overflow not possible since the positive first term
558557
// can only increase an (in range) negative term for addition
559-
// or corresponding negated positive term for subtraction
558+
// or corresponding negated positive term for subtraction.
560559
Scalar::from_int(size.signed_int_max(), size)
561560
} else {
562-
// Positive overflow not possible for similar reason
563-
// max negative
561+
// Positive overflow not possible for similar reason.
564562
Scalar::from_int(size.signed_int_min(), size)
565563
}
566564
} else {

‎compiler/rustc_const_eval/src/interpret/operator.rs

+15-14
Original file line numberDiff line numberDiff line change
@@ -437,23 +437,24 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
437437
};
438438
Ok(ImmTy::from_scalar(res, layout))
439439
}
440-
_ if layout.ty.is_integral() => {
441-
let val = val.to_scalar();
442-
let val = val.to_bits(layout.size)?;
440+
ty::Int(..) => {
441+
let val = val.to_scalar().to_int(layout.size)?;
443442
let res = match un_op {
444-
Not => self.truncate(!val, layout), // bitwise negation, then truncate
445-
Neg => {
446-
// arithmetic negation
447-
assert!(layout.abi.is_signed());
448-
let val = self.sign_extend(val, layout) as i128;
449-
let res = val.wrapping_neg();
450-
let res = res as u128;
451-
// Truncate to target type.
452-
self.truncate(res, layout)
453-
}
443+
Not => !val,
444+
Neg => val.wrapping_neg(),
454445
_ => span_bug!(self.cur_span(), "Invalid integer op {:?}", un_op),
455446
};
456-
Ok(ImmTy::from_uint(res, layout))
447+
let res = ScalarInt::truncate_from_int(res, layout.size).0;
448+
Ok(ImmTy::from_scalar(res.into(), layout))
449+
}
450+
ty::Uint(..) => {
451+
let val = val.to_scalar().to_uint(layout.size)?;
452+
let res = match un_op {
453+
Not => !val,
454+
_ => span_bug!(self.cur_span(), "Invalid unsigned integer op {:?}", un_op),
455+
};
456+
let res = ScalarInt::truncate_from_uint(res, layout.size).0;
457+
Ok(ImmTy::from_scalar(res.into(), layout))
457458
}
458459
ty::RawPtr(..) => {
459460
assert_eq!(un_op, PtrMetadata);

‎compiler/rustc_middle/src/ty/consts/int.rs

+5-10
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,8 @@ impl ScalarInt {
209209

210210
#[inline]
211211
pub fn try_from_uint(i: impl Into<u128>, size: Size) -> Option<Self> {
212-
let data = i.into();
213-
if size.truncate(data) == data { Some(Self::raw(data, size)) } else { None }
212+
let (r, overflow) = Self::truncate_from_uint(i, size);
213+
if overflow { None } else { Some(r) }
214214
}
215215

216216
/// Returns the truncated result, and whether truncation changed the value.
@@ -223,20 +223,15 @@ impl ScalarInt {
223223

224224
#[inline]
225225
pub fn try_from_int(i: impl Into<i128>, size: Size) -> Option<Self> {
226-
let i = i.into();
227-
// `into` performed sign extension, we have to truncate
228-
let truncated = size.truncate(i as u128);
229-
if size.sign_extend(truncated) as i128 == i {
230-
Some(Self::raw(truncated, size))
231-
} else {
232-
None
233-
}
226+
let (r, overflow) = Self::truncate_from_int(i, size);
227+
if overflow { None } else { Some(r) }
234228
}
235229

236230
/// Returns the truncated result, and whether truncation changed the value.
237231
#[inline]
238232
pub fn truncate_from_int(i: impl Into<i128>, size: Size) -> (Self, bool) {
239233
let data = i.into();
234+
// `into` performed sign extension, we have to truncate
240235
let r = Self::raw(size.truncate(data as u128), size);
241236
(r, size.sign_extend(r.data) as i128 != data)
242237
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Make sure we find these even with many checks disabled.
2+
//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
3+
#![feature(core_intrinsics)]
4+
#![feature(custom_mir)]
5+
6+
use std::intrinsics::mir::*;
7+
8+
#[custom_mir(dialect = "runtime", phase = "optimized")]
9+
fn cast(ptr: *const char) -> u32 {
10+
mir! {
11+
{
12+
RET = *ptr as u32; //~ERROR: interpreting an invalid 32-bit value as a char
13+
Return()
14+
}
15+
}
16+
}
17+
18+
pub fn main() {
19+
let v = u32::MAX;
20+
cast(&v as *const u32 as *const char);
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: interpreting an invalid 32-bit value as a char: $HEX
2+
--> $DIR/invalid_char_cast.rs:LL:CC
3+
|
4+
LL | RET = *ptr as u32;
5+
| ^^^^^^^^^^^^^^^^^ interpreting an invalid 32-bit value as a char: $HEX
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `cast` at $DIR/invalid_char_cast.rs:LL:CC
11+
note: inside `main`
12+
--> $DIR/invalid_char_cast.rs:LL:CC
13+
|
14+
LL | cast(&v as *const u32 as *const char);
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Make sure we find these even with many checks disabled.
2+
//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
3+
#![feature(core_intrinsics)]
4+
#![feature(custom_mir)]
5+
6+
use std::intrinsics::mir::*;
7+
8+
#[custom_mir(dialect = "runtime", phase = "optimized")]
9+
fn switch_int(ptr: *const char) {
10+
mir! {
11+
{
12+
match *ptr { //~ERROR: interpreting an invalid 32-bit value as a char
13+
'0' => ret,
14+
_ => ret,
15+
}
16+
}
17+
ret = {
18+
Return()
19+
}
20+
}
21+
}
22+
23+
pub fn main() {
24+
let v = u32::MAX;
25+
switch_int(&v as *const u32 as *const char);
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: Undefined Behavior: interpreting an invalid 32-bit value as a char: $HEX
2+
--> $DIR/invalid_char_match.rs:LL:CC
3+
|
4+
LL | / match *ptr {
5+
LL | | '0' => ret,
6+
LL | | _ => ret,
7+
LL | | }
8+
| |_____________^ interpreting an invalid 32-bit value as a char: $HEX
9+
|
10+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
11+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
12+
= note: BACKTRACE:
13+
= note: inside `switch_int` at $DIR/invalid_char_match.rs:LL:CC
14+
note: inside `main`
15+
--> $DIR/invalid_char_match.rs:LL:CC
16+
|
17+
LL | switch_int(&v as *const u32 as *const char);
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
19+
20+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
21+
22+
error: aborting due to 1 previous error
23+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Make sure we find these even with many checks disabled.
2+
//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
3+
4+
#[derive(Copy, Clone)]
5+
#[allow(unused)]
6+
enum E {A, B, C }
7+
8+
fn cast(ptr: *const E) { unsafe {
9+
let _val = *ptr as u32; //~ERROR: enum value has invalid tag
10+
}}
11+
12+
pub fn main() {
13+
let v = u32::MAX;
14+
cast(&v as *const u32 as *const E);
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: enum value has invalid tag: 0xff
2+
--> $DIR/invalid_enum_cast.rs:LL:CC
3+
|
4+
LL | let _val = *ptr as u32;
5+
| ^^^^^^^^^^^ enum value has invalid tag: 0xff
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `cast` at $DIR/invalid_enum_cast.rs:LL:CC
11+
note: inside `main`
12+
--> $DIR/invalid_enum_cast.rs:LL:CC
13+
|
14+
LL | cast(&v as *const u32 as *const E);
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+

0 commit comments

Comments
 (0)
Failed to load comments.