Skip to content

Commit 5b75ec0

Browse files
committed
fix validation around transmuting copy_op
1 parent c47785f commit 5b75ec0

File tree

5 files changed

+136
-33
lines changed

5 files changed

+136
-33
lines changed

src/librustc_mir/interpret/cast.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
322322
// For now, upcasts are limited to changes in marker
323323
// traits, and hence never actually require an actual
324324
// change to the vtable.
325-
self.copy_op(src, dest)
325+
let val = self.read_value(src)?;
326+
self.write_value(*val, dest)
326327
}
327328
(_, &ty::Dynamic(ref data, _)) => {
328329
// Initial cast from sized to dyn trait

src/librustc_mir/interpret/intrinsics.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
151151
self.write_scalar(val, dest)?;
152152
}
153153
"transmute" => {
154-
// Go through an allocation, to make sure the completely different layouts
155-
// do not pose a problem. (When the user transmutes through a union,
156-
// there will not be a layout mismatch.)
157-
let dest = self.force_allocation(dest)?;
158-
self.copy_op(args[0], dest.into())?;
154+
self.copy_op_transmute(args[0], dest)?;
159155
}
160156

161157
_ => return Ok(false),

src/librustc_mir/interpret/place.rs

Lines changed: 128 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -599,18 +599,47 @@ where
599599
}
600600

601601
/// Write a value to a place
602+
#[inline(always)]
602603
pub fn write_value(
603604
&mut self,
604605
src_val: Value<M::PointerTag>,
605606
dest: PlaceTy<'tcx, M::PointerTag>,
606607
) -> EvalResult<'tcx> {
607-
trace!("write_value: {:?} <- {:?}", *dest, src_val);
608-
// Check that the value actually is okay for that type
608+
self.write_value_no_validate(src_val, dest)?;
609+
609610
if M::ENFORCE_VALIDITY {
610-
// Something changed somewhere, better make sure it matches the type!
611-
let op = OpTy { op: Operand::Immediate(src_val), layout: dest.layout };
612-
self.validate_operand(op, &mut vec![], None, /*const_mode*/false)?;
611+
// Data got changed, better make sure it matches the type!
612+
self.validate_operand(self.place_to_op(dest)?, &mut vec![], None, /*const_mode*/false)?;
613+
}
614+
615+
Ok(())
616+
}
617+
618+
/// Write a value to a place.
619+
/// If you use this you are responsible for validating that things got copied at the
620+
/// right type.
621+
fn write_value_no_validate(
622+
&mut self,
623+
src_val: Value<M::PointerTag>,
624+
dest: PlaceTy<'tcx, M::PointerTag>,
625+
) -> EvalResult<'tcx> {
626+
if cfg!(debug_assertions) {
627+
// This is a very common path, avoid some checks in release mode
628+
assert!(!dest.layout.is_unsized(), "Cannot write unsized data");
629+
match src_val {
630+
Value::Scalar(ScalarMaybeUndef::Scalar(Scalar::Ptr(_))) =>
631+
assert_eq!(self.pointer_size(), dest.layout.size,
632+
"Size mismatch when writing pointer"),
633+
Value::Scalar(ScalarMaybeUndef::Scalar(Scalar::Bits { size, .. })) =>
634+
assert_eq!(Size::from_bytes(size.into()), dest.layout.size,
635+
"Size mismatch when writing bits"),
636+
Value::Scalar(ScalarMaybeUndef::Undef) => {}, // undef can have any size
637+
Value::ScalarPair(_, _) => {
638+
// FIXME: Can we check anything here?
639+
}
640+
}
613641
}
642+
trace!("write_value: {:?} <- {:?}: {}", *dest, src_val, dest.layout.ty);
614643

615644
// See if we can avoid an allocation. This is the counterpart to `try_read_value`,
616645
// but not factored as a separate function.
@@ -627,15 +656,16 @@ where
627656
},
628657
Place::Ptr(mplace) => mplace, // already in memory
629658
};
659+
let dest = MPlaceTy { mplace, layout: dest.layout };
630660

631661
// This is already in memory, write there.
632-
let dest = MPlaceTy { mplace, layout: dest.layout };
633-
self.write_value_to_mplace(src_val, dest)
662+
self.write_value_to_mplace_no_validate(src_val, dest)
634663
}
635664

636-
/// Write a value to memory. This does NOT do validation, so you better had already
637-
/// done that before calling this!
638-
fn write_value_to_mplace(
665+
/// Write a value to memory.
666+
/// If you use this you are responsible for validating that things git copied at the
667+
/// right type.
668+
fn write_value_to_mplace_no_validate(
639669
&mut self,
640670
value: Value<M::PointerTag>,
641671
dest: MPlaceTy<'tcx, M::PointerTag>,
@@ -653,8 +683,17 @@ where
653683
}
654684

655685
let ptr = ptr.to_ptr()?;
686+
// FIXME: We should check that there are dest.layout.size many bytes available in
687+
// memory. The code below is not sufficient, with enough padding it might not
688+
// cover all the bytes!
656689
match value {
657690
Value::Scalar(scalar) => {
691+
match dest.layout.abi {
692+
layout::Abi::Scalar(_) => {}, // fine
693+
_ => bug!("write_value_to_mplace: invalid Scalar layout: {:#?}",
694+
dest.layout)
695+
}
696+
658697
self.memory.write_scalar(
659698
ptr, ptr_align.min(dest.layout.align), scalar, dest.layout.size
660699
)
@@ -670,45 +709,109 @@ where
670709
let b_offset = a_size.abi_align(b_align);
671710
let b_ptr = ptr.offset(b_offset, &self)?.into();
672711

712+
// It is tempting to verify `b_offset` against `layout.fields.offset(1)`,
713+
// but that does not work: We could be a newtype around a pair, then the
714+
// fields do not match the `ScalarPair` components.
715+
673716
self.memory.write_scalar(ptr, ptr_align.min(a_align), a_val, a_size)?;
674717
self.memory.write_scalar(b_ptr, ptr_align.min(b_align), b_val, b_size)
675718
}
676719
}
677720
}
678721

679-
/// Copy the data from an operand to a place
722+
/// Copy the data from an operand to a place. This does not support transmuting!
723+
/// Use `copy_op_transmute` if the layouts could disagree.
724+
#[inline(always)]
680725
pub fn copy_op(
681726
&mut self,
682727
src: OpTy<'tcx, M::PointerTag>,
683728
dest: PlaceTy<'tcx, M::PointerTag>,
684729
) -> EvalResult<'tcx> {
685-
assert!(!src.layout.is_unsized() && !dest.layout.is_unsized(),
730+
self.copy_op_no_validate(src, dest)?;
731+
732+
if M::ENFORCE_VALIDITY {
733+
// Data got changed, better make sure it matches the type!
734+
self.validate_operand(self.place_to_op(dest)?, &mut vec![], None, /*const_mode*/false)?;
735+
}
736+
737+
Ok(())
738+
}
739+
740+
/// Copy the data from an operand to a place. This does not support transmuting!
741+
/// Use `copy_op_transmute` if the layouts could disagree.
742+
/// Also, if you use this you are responsible for validating that things git copied at the
743+
/// right type.
744+
fn copy_op_no_validate(
745+
&mut self,
746+
src: OpTy<'tcx, M::PointerTag>,
747+
dest: PlaceTy<'tcx, M::PointerTag>,
748+
) -> EvalResult<'tcx> {
749+
debug_assert!(!src.layout.is_unsized() && !dest.layout.is_unsized(),
686750
"Cannot copy unsized data");
687-
assert_eq!(src.layout.size, dest.layout.size,
688-
"Size mismatch when copying!\nsrc: {:#?}\ndest: {:#?}", src, dest);
751+
// We do NOT compare the types for equality, because well-typed code can
752+
// actually "transmute" `&mut T` to `&T` in an assignment without a cast.
753+
assert!(src.layout.details == dest.layout.details,
754+
"Layout mismatch when copying!\nsrc: {:#?}\ndest: {:#?}", src, dest);
689755

690756
// Let us see if the layout is simple so we take a shortcut, avoid force_allocation.
691-
let (src_ptr, src_align) = match self.try_read_value(src)? {
692-
Ok(src_val) =>
693-
// Yay, we got a value that we can write directly. We write with the
694-
// *source layout*, because that was used to load, and if they do not match
695-
// this is a transmute we want to support.
696-
return self.write_value(src_val, PlaceTy { place: *dest, layout: src.layout }),
697-
Err(mplace) => mplace.to_scalar_ptr_align(),
757+
let src = match self.try_read_value(src)? {
758+
Ok(src_val) => {
759+
// Yay, we got a value that we can write directly.
760+
return self.write_value_no_validate(src_val, dest);
761+
}
762+
Err(mplace) => mplace,
698763
};
699764
// Slow path, this does not fit into an immediate. Just memcpy.
700-
trace!("copy_op: {:?} <- {:?}", *dest, *src);
765+
trace!("copy_op: {:?} <- {:?}: {}", *dest, src, dest.layout.ty);
766+
701767
let dest = self.force_allocation(dest)?;
768+
let (src_ptr, src_align) = src.to_scalar_ptr_align();
702769
let (dest_ptr, dest_align) = dest.to_scalar_ptr_align();
703770
self.memory.copy(
704771
src_ptr, src_align,
705772
dest_ptr, dest_align,
706-
src.layout.size, false
773+
dest.layout.size, false
707774
)?;
775+
776+
Ok(())
777+
}
778+
779+
/// Copy the data from an operand to a place. The layouts may disagree, but they must
780+
/// have the same size.
781+
pub fn copy_op_transmute(
782+
&mut self,
783+
src: OpTy<'tcx, M::PointerTag>,
784+
dest: PlaceTy<'tcx, M::PointerTag>,
785+
) -> EvalResult<'tcx> {
786+
if src.layout.details == dest.layout.details {
787+
// Fast path: Just use normal `copy_op`
788+
return self.copy_op(src, dest);
789+
}
790+
// We still require the sizes to match
791+
debug_assert!(!src.layout.is_unsized() && !dest.layout.is_unsized(),
792+
"Cannot copy unsized data");
793+
assert!(src.layout.size == dest.layout.size,
794+
"Size mismatch when transmuting!\nsrc: {:#?}\ndest: {:#?}", src, dest);
795+
796+
// The hard case is `ScalarPair`. `src` is already read from memory in this case,
797+
// using `src.layout` to figure out which bytes to use for the 1st and 2nd field.
798+
// We have to write them to `dest` at the offsets they were *read at*, which is
799+
// not necessarily the same as the offsets in `dest.layout`!
800+
// Hence we do the copy with the source layout on both sides. We also make sure to write
801+
// into memory, because if `dest` is a local we would not even have a way to write
802+
// at the `src` offsets; the fact that we came from a different layout would
803+
// just be lost.
804+
let dest = self.force_allocation(dest)?;
805+
self.copy_op_no_validate(
806+
src,
807+
PlaceTy::from(MPlaceTy { mplace: *dest, layout: src.layout }),
808+
)?;
809+
708810
if M::ENFORCE_VALIDITY {
709-
// Something changed somewhere, better make sure it matches the type!
811+
// Data got changed, better make sure it matches the type!
710812
self.validate_operand(dest.into(), &mut vec![], None, /*const_mode*/false)?;
711813
}
814+
712815
Ok(())
713816
}
714817

@@ -734,7 +837,7 @@ where
734837
let ptr = self.allocate(local_layout, MemoryKind::Stack)?;
735838
// We don't have to validate as we can assume the local
736839
// was already valid for its type.
737-
self.write_value_to_mplace(value, ptr)?;
840+
self.write_value_to_mplace_no_validate(value, ptr)?;
738841
let mplace = ptr.mplace;
739842
// Update the local
740843
*self.stack[frame].locals[local].access_mut()? =

src/librustc_mir/interpret/terminator.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
222222
if !Self::check_argument_compat(caller_arg.layout, callee_arg.layout) {
223223
return err!(FunctionArgMismatch(caller_arg.layout.ty, callee_arg.layout.ty));
224224
}
225-
self.copy_op(caller_arg, callee_arg)
225+
// We allow some transmutes here
226+
self.copy_op_transmute(caller_arg, callee_arg)
226227
}
227228

228229
/// Call this function -- pushing the stack frame and initializing the arguments.

src/librustc_mir/interpret/validity.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,9 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
218218
return validation_failure!("unaligned reference", path),
219219
_ =>
220220
return validation_failure!(
221-
"dangling (deallocated) reference", path
221+
"dangling (out-of-bounds) reference (might be NULL at \
222+
run-time)",
223+
path
222224
),
223225
}
224226
}

0 commit comments

Comments
 (0)