Skip to content

Commit a3770c2

Browse files
committed
do not accept out-of-bounds pointers in enum discriminants, they might be NULL
1 parent ffb6ba0 commit a3770c2

File tree

9 files changed

+86
-48
lines changed

9 files changed

+86
-48
lines changed

src/librustc/ich/impls_ty.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ impl_stable_hash_for!(
451451
FunctionRetMismatch(a, b),
452452
NoMirFor(s),
453453
UnterminatedCString(ptr),
454-
PointerOutOfBounds { ptr, access, allocation_size },
454+
PointerOutOfBounds { ptr, check, allocation_size },
455455
InvalidBoolOp(bop),
456456
Unimplemented(s),
457457
BoundsCheck { len, index },
@@ -471,6 +471,11 @@ impl_stable_hash_for!(
471471
}
472472
);
473473

474+
impl_stable_hash_for!(enum mir::interpret::InboundsCheck {
475+
Live,
476+
MaybeDead
477+
});
478+
474479
impl_stable_hash_for!(enum mir::interpret::Lock {
475480
NoLock,
476481
WriteLock(dl),

src/librustc/mir/interpret/allocation.rs

+8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ use mir;
1919
use std::ops::{Deref, DerefMut};
2020
use rustc_data_structures::sorted_map::SortedMap;
2121

22+
/// Used by `check_bounds` to indicate whether the pointer needs to be just inbounds
23+
/// or also inbounds of a *live* allocation.
24+
#[derive(Debug, Copy, Clone, RustcEncodable, RustcDecodable)]
25+
pub enum InboundsCheck {
26+
Live,
27+
MaybeDead,
28+
}
29+
2230
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)]
2331
pub struct Allocation<Tag=(),Extra=()> {
2432
/// The actual bytes of the allocation.

src/librustc/mir/interpret/error.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use ty::{Ty, layout};
1515
use ty::layout::{Size, Align, LayoutError};
1616
use rustc_target::spec::abi::Abi;
1717

18-
use super::{Pointer, ScalarMaybeUndef};
18+
use super::{Pointer, InboundsCheck, ScalarMaybeUndef};
1919

2020
use backtrace::Backtrace;
2121

@@ -243,7 +243,7 @@ pub enum EvalErrorKind<'tcx, O> {
243243
InvalidDiscriminant(ScalarMaybeUndef),
244244
PointerOutOfBounds {
245245
ptr: Pointer,
246-
access: bool,
246+
check: InboundsCheck,
247247
allocation_size: Size,
248248
},
249249
InvalidNullPointerUsage,
@@ -457,9 +457,13 @@ impl<'tcx, O: fmt::Debug> fmt::Debug for EvalErrorKind<'tcx, O> {
457457
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458458
use self::EvalErrorKind::*;
459459
match *self {
460-
PointerOutOfBounds { ptr, access, allocation_size } => {
461-
write!(f, "{} at offset {}, outside bounds of allocation {} which has size {}",
462-
if access { "memory access" } else { "pointer computed" },
460+
PointerOutOfBounds { ptr, check, allocation_size } => {
461+
write!(f, "Pointer must be in-bounds{} at offset {}, but is outside bounds of \
462+
allocation {} which has size {}",
463+
match check {
464+
InboundsCheck::Live => " and live",
465+
InboundsCheck::MaybeDead => "",
466+
},
463467
ptr.offset.bytes(), ptr.alloc_id, allocation_size.bytes())
464468
},
465469
ValidationFailure(ref err) => {

src/librustc/mir/interpret/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub use self::error::{
2828
pub use self::value::{Scalar, ConstValue, ScalarMaybeUndef};
2929

3030
pub use self::allocation::{
31-
Allocation, AllocationExtra,
31+
InboundsCheck, Allocation, AllocationExtra,
3232
Relocations, UndefMask,
3333
};
3434

src/librustc_mir/interpret/memory.rs

+27-23
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use rustc_data_structures::fx::{FxHashSet, FxHashMap};
2828
use syntax::ast::Mutability;
2929

3030
use super::{
31-
Pointer, AllocId, Allocation, ConstValue, GlobalId, AllocationExtra,
31+
Pointer, AllocId, Allocation, ConstValue, GlobalId, AllocationExtra, InboundsCheck,
3232
EvalResult, Scalar, EvalErrorKind, AllocType, PointerArithmetic,
3333
Machine, AllocMap, MayLeak, ScalarMaybeUndef, ErrorHandled,
3434
};
@@ -249,17 +249,11 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
249249
// Check non-NULL/Undef, extract offset
250250
let (offset, alloc_align) = match ptr {
251251
Scalar::Ptr(ptr) => {
252-
let (size, align) = self.get_size_and_align(ptr.alloc_id);
253252
// check this is not NULL -- which we can ensure only if this is in-bounds
254253
// of some (potentially dead) allocation.
255-
if ptr.offset > size {
256-
return err!(PointerOutOfBounds {
257-
ptr: ptr.erase_tag(),
258-
access: true,
259-
allocation_size: size,
260-
});
261-
};
262-
// keep data for alignment check
254+
self.check_bounds_ptr(ptr, InboundsCheck::MaybeDead)?;
255+
// data required for alignment check
256+
let (_, align) = self.get_size_and_align(ptr.alloc_id);
263257
(ptr.offset.bytes(), align)
264258
}
265259
Scalar::Bits { bits, size } => {
@@ -293,18 +287,28 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
293287

294288
/// Check if the pointer is "in-bounds". Notice that a pointer pointing at the end
295289
/// of an allocation (i.e., at the first *inaccessible* location) *is* considered
296-
/// in-bounds! This follows C's/LLVM's rules. The `access` boolean is just used
297-
/// for the error message.
298-
/// If you want to check bounds before doing a memory access, be sure to
299-
/// check the pointer one past the end of your access, then everything will
300-
/// work out exactly.
301-
pub fn check_bounds_ptr(&self, ptr: Pointer<M::PointerTag>, access: bool) -> EvalResult<'tcx> {
302-
let alloc = self.get(ptr.alloc_id)?;
303-
let allocation_size = alloc.bytes.len() as u64;
290+
/// in-bounds! This follows C's/LLVM's rules. `check` indicates whether we
291+
/// additionally require the pointer to be pointing to a *live* (still allocated)
292+
/// allocation.
293+
/// If you want to check bounds before doing a memory access, better use `check_bounds`.
294+
pub fn check_bounds_ptr(
295+
&self,
296+
ptr: Pointer<M::PointerTag>,
297+
check: InboundsCheck,
298+
) -> EvalResult<'tcx> {
299+
let allocation_size = match check {
300+
InboundsCheck::Live => {
301+
let alloc = self.get(ptr.alloc_id)?;
302+
alloc.bytes.len() as u64
303+
}
304+
InboundsCheck::MaybeDead => {
305+
self.get_size_and_align(ptr.alloc_id).0.bytes()
306+
}
307+
};
304308
if ptr.offset.bytes() > allocation_size {
305309
return err!(PointerOutOfBounds {
306310
ptr: ptr.erase_tag(),
307-
access,
311+
check,
308312
allocation_size: Size::from_bytes(allocation_size),
309313
});
310314
}
@@ -317,10 +321,10 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
317321
&self,
318322
ptr: Pointer<M::PointerTag>,
319323
size: Size,
320-
access: bool
324+
check: InboundsCheck,
321325
) -> EvalResult<'tcx> {
322326
// if ptr.offset is in bounds, then so is ptr (because offset checks for overflow)
323-
self.check_bounds_ptr(ptr.offset(size, &*self)?, access)
327+
self.check_bounds_ptr(ptr.offset(size, &*self)?, check)
324328
}
325329
}
326330

@@ -626,7 +630,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
626630
) -> EvalResult<'tcx, &[u8]> {
627631
assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`");
628632
self.check_align(ptr.into(), align)?;
629-
self.check_bounds(ptr, size, true)?;
633+
self.check_bounds(ptr, size, InboundsCheck::Live)?;
630634

631635
if check_defined_and_ptr {
632636
self.check_defined(ptr, size)?;
@@ -677,7 +681,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
677681
) -> EvalResult<'tcx, &mut [u8]> {
678682
assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`");
679683
self.check_align(ptr.into(), align)?;
680-
self.check_bounds(ptr, size, true)?;
684+
self.check_bounds(ptr, size, InboundsCheck::Live)?;
681685

682686
self.mark_definedness(ptr, size, true)?;
683687
self.clear_relocations(ptr, size)?;

src/librustc_mir/interpret/operand.rs

+13-10
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use rustc::ty::layout::{self, Size, LayoutOf, TyLayout, HasDataLayout, IntegerEx
1919
use rustc::mir::interpret::{
2020
GlobalId, AllocId,
2121
ConstValue, Pointer, Scalar,
22-
EvalResult, EvalErrorKind
22+
EvalResult, EvalErrorKind, InboundsCheck,
2323
};
2424
use super::{EvalContext, Machine, MemPlace, MPlaceTy, MemoryKind};
2525
pub use rustc::mir::interpret::ScalarMaybeUndef;
@@ -647,24 +647,27 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
647647
let variants_start = niche_variants.start().as_u32() as u128;
648648
let variants_end = niche_variants.end().as_u32() as u128;
649649
match raw_discr {
650-
ScalarMaybeUndef::Scalar(Scalar::Ptr(_)) => {
651-
// The niche must be just 0 (which a pointer value never is)
652-
assert!(niche_start == 0);
653-
assert!(variants_start == variants_end);
650+
ScalarMaybeUndef::Scalar(Scalar::Ptr(ptr)) => {
651+
// The niche must be just 0 (which an inbounds pointer value never is)
652+
let ptr_valid = niche_start == 0 && variants_start == variants_end &&
653+
self.memory.check_bounds_ptr(ptr, InboundsCheck::MaybeDead).is_ok();
654+
if !ptr_valid {
655+
return err!(InvalidDiscriminant(raw_discr.erase_tag()));
656+
}
654657
(dataful_variant.as_u32() as u128, dataful_variant)
655658
},
656659
ScalarMaybeUndef::Scalar(Scalar::Bits { bits: raw_discr, size }) => {
657660
assert_eq!(size as u64, discr_val.layout.size.bytes());
658-
let discr = raw_discr.wrapping_sub(niche_start)
661+
let adjusted_discr = raw_discr.wrapping_sub(niche_start)
659662
.wrapping_add(variants_start);
660-
if variants_start <= discr && discr <= variants_end {
661-
let index = discr as usize;
662-
assert_eq!(index as u128, discr);
663+
if variants_start <= adjusted_discr && adjusted_discr <= variants_end {
664+
let index = adjusted_discr as usize;
665+
assert_eq!(index as u128, adjusted_discr);
663666
assert!(index < rval.layout.ty
664667
.ty_adt_def()
665668
.expect("tagged layout for non adt")
666669
.variants.len());
667-
(discr, VariantIdx::from_usize(index))
670+
(adjusted_discr, VariantIdx::from_usize(index))
668671
} else {
669672
(dataful_variant.as_u32() as u128, dataful_variant)
670673
}

src/librustc_mir/interpret/validity.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rustc::ty::layout::{self, Size, Align, TyLayout, LayoutOf, VariantIdx};
1717
use rustc::ty;
1818
use rustc_data_structures::fx::FxHashSet;
1919
use rustc::mir::interpret::{
20-
Scalar, AllocType, EvalResult, EvalErrorKind,
20+
Scalar, AllocType, EvalResult, EvalErrorKind, InboundsCheck,
2121
};
2222

2323
use super::{
@@ -394,7 +394,8 @@ impl<'rt, 'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>>
394394
}
395395
// Maintain the invariant that the place we are checking is
396396
// already verified to be in-bounds.
397-
try_validation!(self.ecx.memory.check_bounds(ptr, size, false),
397+
try_validation!(
398+
self.ecx.memory.check_bounds(ptr, size, InboundsCheck::Live),
398399
"dangling (not entirely in bounds) reference", self.path);
399400
}
400401
// Check if we have encountered this pointer+layout combination

src/test/ui/consts/const-eval/ub-enum.rs

+5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ union TransmuteEnum2 {
3939
in3: (),
4040
out1: Enum2,
4141
out2: Wrap<Enum2>, // something wrapping the enum so that we test layout first, not enum
42+
out3: Option<Enum2>,
4243
}
4344
const BAD_ENUM2: Enum2 = unsafe { TransmuteEnum2 { in1: 0 }.out1 };
4445
//~^ ERROR is undefined behavior
@@ -51,6 +52,10 @@ const BAD_ENUM4: Wrap<Enum2> = unsafe { TransmuteEnum2 { in2: &0 }.out2 };
5152
const BAD_ENUM_UNDEF: [Enum2; 2] = [unsafe { TransmuteEnum2 { in3: () }.out1 }; 2];
5253
//~^ ERROR is undefined behavior
5354

55+
// Pointer value in an enum with a niche that is not just 0.
56+
const BAD_ENUM_PTR: Option<Enum2> = unsafe { TransmuteEnum2 { in2: &0 }.out3 };
57+
//~^ ERROR is undefined behavior
58+
5459
// Invalid enum field content (mostly to test printing of paths for enum tuple
5560
// variants and tuples).
5661
union TransmuteChar {

src/test/ui/consts/const-eval/ub-enum.stderr

+14-6
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,53 @@ LL | const BAD_ENUM: Enum = unsafe { TransmuteEnum { a: &1 }.out };
77
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
88

99
error[E0080]: it is undefined behavior to use this value
10-
--> $DIR/ub-enum.rs:43:1
10+
--> $DIR/ub-enum.rs:44:1
1111
|
1212
LL | const BAD_ENUM2: Enum2 = unsafe { TransmuteEnum2 { in1: 0 }.out1 };
1313
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0, but expected a valid enum discriminant
1414
|
1515
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
1616

1717
error[E0080]: it is undefined behavior to use this value
18-
--> $DIR/ub-enum.rs:45:1
18+
--> $DIR/ub-enum.rs:46:1
1919
|
2020
LL | const BAD_ENUM3: Enum2 = unsafe { TransmuteEnum2 { in2: &0 }.out1 };
2121
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected a valid enum discriminant
2222
|
2323
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
2424

2525
error[E0080]: it is undefined behavior to use this value
26-
--> $DIR/ub-enum.rs:47:1
26+
--> $DIR/ub-enum.rs:48:1
2727
|
2828
LL | const BAD_ENUM4: Wrap<Enum2> = unsafe { TransmuteEnum2 { in2: &0 }.out2 };
2929
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected something that cannot possibly fail to be in the range 2..=2
3030
|
3131
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
3232

3333
error[E0080]: it is undefined behavior to use this value
34-
--> $DIR/ub-enum.rs:51:1
34+
--> $DIR/ub-enum.rs:52:1
3535
|
3636
LL | const BAD_ENUM_UNDEF: [Enum2; 2] = [unsafe { TransmuteEnum2 { in3: () }.out1 }; 2];
3737
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered uninitialized bytes at [0], but expected a valid enum discriminant
3838
|
3939
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
4040

4141
error[E0080]: it is undefined behavior to use this value
42-
--> $DIR/ub-enum.rs:61:1
42+
--> $DIR/ub-enum.rs:56:1
43+
|
44+
LL | const BAD_ENUM_PTR: Option<Enum2> = unsafe { TransmuteEnum2 { in2: &0 }.out3 };
45+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected a valid enum discriminant
46+
|
47+
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
48+
49+
error[E0080]: it is undefined behavior to use this value
50+
--> $DIR/ub-enum.rs:66:1
4351
|
4452
LL | const BAD_ENUM_CHAR: Option<(char, char)> = Some(('x', unsafe { TransmuteChar { a: !0 }.b }));
4553
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 4294967295 at .<downcast-variant(Some)>.0.1, but expected something less or equal to 1114111
4654
|
4755
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
4856

49-
error: aborting due to 6 previous errors
57+
error: aborting due to 7 previous errors
5058

5159
For more information about this error, try `rustc --explain E0080`.

0 commit comments

Comments
 (0)