Skip to content

Commit fdd962e

Browse files
committed
validate enum discriminant whenever it is read
1 parent fa5326f commit fdd962e

File tree

8 files changed

+64
-65
lines changed

8 files changed

+64
-65
lines changed

src/librustc/mir/interpret/error.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ impl<'tcx, O> EvalErrorKind<'tcx, O> {
303303
InvalidBool =>
304304
"invalid boolean value read",
305305
InvalidDiscriminant =>
306-
"invalid enum discriminant value read",
306+
"invalid enum discriminant value read or written",
307307
PointerOutOfBounds { .. } =>
308308
"pointer offset outside bounds of allocation",
309309
InvalidNullPointerUsage =>

src/librustc_mir/const_eval.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ pub fn const_variant_index<'a, 'tcx>(
382382
trace!("const_variant_index: {:?}, {:?}", instance, val);
383383
let ecx = mk_eval_cx(tcx, instance, param_env).unwrap();
384384
let op = ecx.const_to_op(val)?;
385-
ecx.read_discriminant_as_variant_index(op)
385+
Ok(ecx.read_discriminant(op)?.1)
386386
}
387387

388388
pub fn const_to_allocation_provider<'a, 'tcx>(

src/librustc_mir/interpret/operand.rs

+27-33
Original file line numberDiff line numberDiff line change
@@ -544,34 +544,11 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
544544
self.const_value_to_op(cv.val)
545545
}
546546

547-
/// reads a tag and produces the corresponding variant index
548-
pub fn read_discriminant_as_variant_index(
547+
/// Read discriminant, return the runtime value as well as the variant index.
548+
pub fn read_discriminant(
549549
&self,
550550
rval: OpTy<'tcx>,
551-
) -> EvalResult<'tcx, usize> {
552-
match rval.layout.variants {
553-
layout::Variants::Single { index } => Ok(index),
554-
layout::Variants::Tagged { .. } => {
555-
let discr_val = self.read_discriminant_value(rval)?;
556-
rval.layout.ty
557-
.ty_adt_def()
558-
.expect("tagged layout for non adt")
559-
.discriminants(self.tcx.tcx)
560-
.position(|var| var.val == discr_val)
561-
.ok_or_else(|| EvalErrorKind::InvalidDiscriminant.into())
562-
}
563-
layout::Variants::NicheFilling { .. } => {
564-
let discr_val = self.read_discriminant_value(rval)?;
565-
assert_eq!(discr_val as usize as u128, discr_val);
566-
Ok(discr_val as usize)
567-
},
568-
}
569-
}
570-
571-
pub fn read_discriminant_value(
572-
&self,
573-
rval: OpTy<'tcx>,
574-
) -> EvalResult<'tcx, u128> {
551+
) -> EvalResult<'tcx, (u128, usize)> {
575552
trace!("read_discriminant_value {:#?}", rval.layout);
576553
if rval.layout.abi == layout::Abi::Uninhabited {
577554
return err!(Unreachable);
@@ -582,20 +559,21 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
582559
let discr_val = rval.layout.ty.ty_adt_def().map_or(
583560
index as u128,
584561
|def| def.discriminant_for_variant(*self.tcx, index).val);
585-
return Ok(discr_val);
562+
return Ok((discr_val, index));
586563
}
587564
layout::Variants::Tagged { .. } |
588565
layout::Variants::NicheFilling { .. } => {},
589566
}
567+
// read raw discriminant value
590568
let discr_op = self.operand_field(rval, 0)?;
591569
let discr_val = self.read_value(discr_op)?;
592-
trace!("discr value: {:?}", discr_val);
593570
let raw_discr = discr_val.to_scalar()?;
571+
trace!("discr value: {:?}", raw_discr);
572+
// post-process
594573
Ok(match rval.layout.variants {
595574
layout::Variants::Single { .. } => bug!(),
596-
// FIXME: We should catch invalid discriminants here!
597575
layout::Variants::Tagged { .. } => {
598-
if discr_val.layout.ty.is_signed() {
576+
let real_discr = if discr_val.layout.ty.is_signed() {
599577
let i = raw_discr.to_bits(discr_val.layout.size)? as i128;
600578
// going from layout tag type to typeck discriminant type
601579
// requires first sign extending with the layout discriminant
@@ -612,7 +590,15 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
612590
(truncatee << shift) >> shift
613591
} else {
614592
raw_discr.to_bits(discr_val.layout.size)?
615-
}
593+
};
594+
// Make sure we catch invalid discriminants
595+
let index = rval.layout.ty
596+
.ty_adt_def()
597+
.expect("tagged layout for non adt")
598+
.discriminants(self.tcx.tcx)
599+
.position(|var| var.val == real_discr)
600+
.ok_or_else(|| EvalErrorKind::InvalidDiscriminant)?;
601+
(real_discr, index)
616602
},
617603
layout::Variants::NicheFilling {
618604
dataful_variant,
@@ -622,8 +608,9 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
622608
} => {
623609
let variants_start = *niche_variants.start() as u128;
624610
let variants_end = *niche_variants.end() as u128;
625-
match raw_discr {
611+
let real_discr = match raw_discr {
626612
Scalar::Ptr(_) => {
613+
// The niche must be just 0 (which a pointer value never is)
627614
assert!(niche_start == 0);
628615
assert!(variants_start == variants_end);
629616
dataful_variant as u128
@@ -638,7 +625,14 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
638625
dataful_variant as u128
639626
}
640627
},
641-
}
628+
};
629+
let index = real_discr as usize;
630+
assert_eq!(index as u128, real_discr);
631+
assert!(index < rval.layout.ty
632+
.ty_adt_def()
633+
.expect("tagged layout for non adt")
634+
.variants.len());
635+
(real_discr, index)
642636
}
643637
})
644638
}

src/librustc_mir/interpret/place.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -702,22 +702,23 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
702702
Ok(MPlaceTy::from_aligned_ptr(ptr, layout))
703703
}
704704

705-
pub fn write_discriminant_value(
705+
pub fn write_discriminant_index(
706706
&mut self,
707707
variant_index: usize,
708708
dest: PlaceTy<'tcx>,
709709
) -> EvalResult<'tcx> {
710710
match dest.layout.variants {
711711
layout::Variants::Single { index } => {
712712
if index != variant_index {
713-
// If the layout of an enum is `Single`, all
714-
// other variants are necessarily uninhabited.
715-
assert_eq!(dest.layout.for_variant(&self, variant_index).abi,
716-
layout::Abi::Uninhabited);
713+
return err!(InvalidDiscriminant);
717714
}
718715
}
719716
layout::Variants::Tagged { ref tag, .. } => {
720-
let discr_val = dest.layout.ty.ty_adt_def().unwrap()
717+
let adt_def = dest.layout.ty.ty_adt_def().unwrap();
718+
if variant_index >= adt_def.variants.len() {
719+
return err!(InvalidDiscriminant);
720+
}
721+
let discr_val = adt_def
721722
.discriminant_for_variant(*self.tcx, variant_index)
722723
.val;
723724

@@ -740,6 +741,9 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
740741
niche_start,
741742
..
742743
} => {
744+
if variant_index >= dest.layout.ty.ty_adt_def().unwrap().variants.len() {
745+
return err!(InvalidDiscriminant);
746+
}
743747
if variant_index != dataful_variant {
744748
let niche_dest =
745749
self.place_field(dest, 0)?;

src/librustc_mir/interpret/step.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
127127
variant_index,
128128
} => {
129129
let dest = self.eval_place(place)?;
130-
self.write_discriminant_value(variant_index, dest)?;
130+
self.write_discriminant_index(variant_index, dest)?;
131131
}
132132

133133
// Mark locals as alive
@@ -222,7 +222,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
222222
Aggregate(ref kind, ref operands) => {
223223
let (dest, active_field_index) = match **kind {
224224
mir::AggregateKind::Adt(adt_def, variant_index, _, _, active_field_index) => {
225-
self.write_discriminant_value(variant_index, dest)?;
225+
self.write_discriminant_index(variant_index, dest)?;
226226
if adt_def.is_enum() {
227227
(self.place_downcast(dest, variant_index)?, active_field_index)
228228
} else {
@@ -312,7 +312,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
312312

313313
Discriminant(ref place) => {
314314
let place = self.eval_place(place)?;
315-
let discr_val = self.read_discriminant_value(self.place_to_op(place)?)?;
315+
let discr_val = self.read_discriminant(self.place_to_op(place)?)?.0;
316316
let size = dest.layout.size.bytes() as u8;
317317
self.write_scalar(Scalar::Bits {
318318
bits: discr_val,

src/librustc_mir/interpret/validity.rs

+19-18
Original file line numberDiff line numberDiff line change
@@ -198,25 +198,25 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
198198
seen: &mut FxHashSet<(OpTy<'tcx>)>,
199199
todo: &mut Vec<(OpTy<'tcx>, Vec<PathElem>)>,
200200
) -> EvalResult<'tcx> {
201-
trace!("validate_mplace: {:?}, {:#?}", *dest, dest.layout);
201+
trace!("validate_operand: {:?}, {:#?}", *dest, dest.layout);
202202

203203
// Find the right variant. We have to handle this as a prelude, not via
204204
// proper recursion with the new inner layout, to be able to later nicely
205205
// print the field names of the enum field that is being accessed.
206206
let (variant, dest) = match dest.layout.variants {
207-
layout::Variants::NicheFilling { niche: ref tag, .. } |
208-
layout::Variants::Tagged { ref tag, .. } => {
209-
let size = tag.value.size(self);
210-
// we first read the tag value as scalar, to be able to validate it
211-
let tag_mplace = self.operand_field(dest, 0)?;
212-
let tag_value = self.read_scalar(tag_mplace)?;
213-
path.push(PathElem::Tag);
214-
self.validate_scalar(
215-
tag_value, size, tag, &path, tag_mplace.layout.ty
216-
)?;
217-
path.pop(); // remove the element again
218-
// then we read it again to get the index, to continue
219-
let variant = self.read_discriminant_as_variant_index(dest.into())?;
207+
layout::Variants::NicheFilling { .. } |
208+
layout::Variants::Tagged { .. } => {
209+
let variant = match self.read_discriminant(dest) {
210+
Ok(res) => res.1,
211+
Err(err) => match err.kind {
212+
EvalErrorKind::InvalidDiscriminant |
213+
EvalErrorKind::ReadPointerAsBytes =>
214+
return validation_failure!(
215+
"invalid enum discriminant", path
216+
),
217+
_ => return Err(err),
218+
}
219+
};
220220
let inner_dest = self.operand_downcast(dest, variant)?;
221221
// Put the variant projection onto the path, as a field
222222
path.push(PathElem::Field(dest.layout.ty
@@ -258,17 +258,17 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
258258
let alloc_kind = self.tcx.alloc_map.lock().get(ptr.alloc_id);
259259
if let Some(AllocType::Static(did)) = alloc_kind {
260260
// statics from other crates are already checked.
261-
// extern statics should not be validated as they have no body.
261+
// extern statics cannot be validated as they have no body.
262262
if !did.is_local() || self.tcx.is_foreign_item(did) {
263263
return Ok(());
264264
}
265265
}
266266
if value.layout.ty.builtin_deref(false).is_some() {
267-
trace!("Recursing below ptr {:#?}", value);
268267
let ptr_op = self.ref_to_mplace(value)?.into();
269268
// we have not encountered this pointer+layout combination
270269
// before.
271270
if seen.insert(ptr_op) {
271+
trace!("Recursing below ptr {:#?}", *value);
272272
todo.push((ptr_op, path_clone_and_deref(path)));
273273
}
274274
}
@@ -284,6 +284,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
284284
// See https://github.com/rust-lang/rust/issues/32836#issuecomment-406875389
285285
},
286286
layout::FieldPlacement::Array { .. } => {
287+
// FIXME: For a TyStr, check that this is valid UTF-8.
287288
// Skips for ZSTs; we could have an empty array as an immediate
288289
if !dest.layout.is_zst() {
289290
let dest = dest.to_mem_place(); // arrays cannot be immediate
@@ -316,8 +317,9 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
316317
let unpacked_ptr = self.unpack_unsized_mplace(ptr)?.into();
317318
// for safe ptrs, recursively check it
318319
if !dest.layout.ty.is_unsafe_ptr() {
319-
trace!("Recursing below fat ptr {:?} (unpacked: {:?})", ptr, unpacked_ptr);
320320
if seen.insert(unpacked_ptr) {
321+
trace!("Recursing below fat ptr {:?} (unpacked: {:?})",
322+
ptr, unpacked_ptr);
321323
todo.push((unpacked_ptr, path_clone_and_deref(path)));
322324
}
323325
}
@@ -329,7 +331,6 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
329331
self.validate_operand(field, path, seen, todo)?;
330332
path.truncate(path_len);
331333
}
332-
// FIXME: For a TyStr, check that this is valid UTF-8.
333334
}
334335
}
335336
}

src/test/ui/consts/const-eval/double_check2.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ LL | / static FOO: (&Foo, &Bar) = unsafe {( //~ undefined behavior
55
LL | | Union { usize: &BAR }.foo,
66
LL | | Union { usize: &BAR }.bar,
77
LL | | )};
8-
| |___^ type validation failed: encountered 5 at .1.<deref>.<enum-tag>, but expected something in the range 42..=99
8+
| |___^ type validation failed: encountered invalid enum discriminant at .1.<deref>
99
|
1010
= 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
1111

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ error[E0080]: this constant likely exhibits undefined behavior
22
--> $DIR/ub-enum.rs:22:1
33
|
44
LL | const BAD_ENUM: Enum = unsafe { TransmuteEnum { a: &1 }.b };
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered pointer at .<enum-tag>, but expected something in the range 0..=0
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered invalid enum discriminant
66
|
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]: this constant likely exhibits undefined behavior
1010
--> $DIR/ub-enum.rs:35:1
1111
|
1212
LL | const BAD_ENUM2 : Enum2 = unsafe { TransmuteEnum2 { a: 0 }.b };
13-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0 at .<enum-tag>, but expected something in the range 2..=2
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered invalid 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

0 commit comments

Comments
 (0)