Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make uninit checks stricter but avoid issues for old hyper #99389

Closed
wants to merge 8 commits into from
Closed
7 changes: 5 additions & 2 deletions compiler/rustc_const_eval/src/might_permit_raw_init.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::const_eval::CompileTimeInterpreter;
use crate::interpret::{InterpCx, MemoryKind, OpTy};
use rustc_middle::ty::layout::LayoutCx;
use rustc_middle::ty::{layout::TyAndLayout, ParamEnv, TyCtxt};
use rustc_middle::ty::{
layout::{self, TyAndLayout},
ParamEnv, TyCtxt,
};
use rustc_session::Limit;
use rustc_target::abi::InitKind;

Expand Down Expand Up @@ -35,6 +38,6 @@ pub fn might_permit_raw_init<'tcx>(
cx.validate_operand(&ot).is_ok()
} else {
let layout_cx = LayoutCx { tcx, param_env: ParamEnv::reveal_all() };
ty.might_permit_raw_init(&layout_cx, kind)
layout::might_permit_raw_init(ty, &layout_cx, kind)
}
}
151 changes: 151 additions & 0 deletions compiler/rustc_middle/src/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3492,3 +3492,154 @@ fn make_thin_self_ptr<'tcx>(
..tcx.layout_of(ty::ParamEnv::reveal_all().and(unit_ptr_ty)).unwrap()
}
}

/// Determines if this type permits "raw" initialization by just transmuting some
/// memory into an instance of `T`.
///
/// If called with InitKind::Uninit, this function must always panic if a 0x01 filled buffer would
/// cause LLVM UB.
///
/// This code is intentionally conservative, and will not detect
/// * making uninitialized types who have a full valid range (ints, floats, raw pointers)
/// * uninit `&T` where T has align 1 size 0 (only inside arrays).
/// * zero init enums where a discriminant with tag 0 exists, but is invalid to be zeroed
/// * zero init type that does not allow zero init (only inside arrays)
///
/// A strict form of these checks that uses const evaluation exists in
/// `rustc_const_eval::might_permit_raw_init`, and a tracking issue for making these checks
/// stricter is <https://github.com/rust-lang/rust/issues/66151>.
///
/// FIXME: Once all the conservatism is removed from here, and the checks are ran by default,
/// we can use the const evaluation checks always instead.
pub fn might_permit_raw_init<'tcx, C>(this: TyAndLayout<'tcx>, cx: &C, init_kind: InitKind) -> bool
where
C: HasDataLayout + ty::layout::HasParamEnv<'tcx> + ty::layout::HasTyCtxt<'tcx>,
{
return might_permit_raw_init_inner(this, cx, init_kind, false);

fn might_permit_raw_init_inner<'tcx, C>(
this: TyAndLayout<'tcx>,
cx: &C,
init_kind: InitKind,
inside_array: bool,
) -> bool
where
C: HasDataLayout + ty::layout::HasParamEnv<'tcx> + ty::layout::HasTyCtxt<'tcx>,
{
let scalar_allows_raw_init = move |s: Scalar| -> bool {
match init_kind {
InitKind::Zero => {
// The range must contain 0.
s.valid_range(cx).contains(0)
}
InitKind::Uninit => {
// FIXME(#66151) This should be "is the type a union", but that's pending on a
// resolution to https://github.com/rust-lang/unsafe-code-guidelines/issues/71
// And likely a large number of crates fail with those rules, though no crater
// run has been done yet.
//
// The range must include all values.
s.is_always_valid(cx)
}
}
};

// These are bypasses in order to try not to break quite as many crates so quickly.
// They are being done only if we're inside an array, to ensure we don't panic on *less*
// things than we did before this change.
// See: https://github.com/rust-lang/rust/pull/99389
if inside_array {
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
match init_kind {
// We panic if creating this type with all 0x01 bytes would
// cause LLVM UB.
//
// Therefore, in order for us to not panic, it must either be a
// reference to [T] where T has align 1 (where we don't statically know
// the size, so we don't emit any dereferenceable), or a reference to str
// which acts much like a [u8].
//
// We *do* need to panic for &dyn Trait, even though the layout of dyn Trait is
// size 0 align 1, because &dyn Trait holds a reference to the vtable, which
// must be aligned and dereferenceable.
//
// This even applies to *const dyn Trait, which holds a reference to the vtable
// and therefore must be valid, so 1-initialization is not okay there.
//
// If this bypass didn't exist, old versions of `hyper` with no semver compatible
// fix (0.11, 0.12, 0.13) would panic, as they make uninit &[u8] and &str.
InitKind::Uninit => {
if let ty::Ref(_, inner, _) = this.ty.kind() {
if let ty::Slice(slice_inner) = inner.kind() {
let penv = ty::ParamEnv::reveal_all().and(*slice_inner);

if let Ok(l) = cx.tcx().layout_of(penv) {
return l.layout.align().abi == Align::ONE;
}
}

if ty::Str == *inner.kind() {
return true;
}
}
}
// FIXME(#66151) We need to ignore all forms of zero data being made inside an
// array, because old versions of crossbeam make zeroed data, sometimes at an
// arbitrary type chosen by the user (such as for crossbeam-channel).
InitKind::Zero => {
return true;
}
}
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
}

// Check the ABI.
let valid = match this.abi {
Abi::Uninhabited => false, // definitely UB
Abi::Scalar(s) => scalar_allows_raw_init(s),
Abi::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2),
Abi::Vector { element: s, count } => count == 0 || scalar_allows_raw_init(s),
Abi::Aggregate { .. } => true, // Fields are checked below.
};
if !valid {
// This is definitely not okay.
return false;
}

// If we have not found an error yet, we need to recursively descend into fields.
match &this.fields {
FieldsShape::Primitive | FieldsShape::Union { .. } => {}
FieldsShape::Array { count, .. } => {
if *count > 0
&& !might_permit_raw_init_inner(
this.field(cx, 0),
cx,
init_kind,
/*inside_array*/ true,
)
{
// Found non empty array with a type that is unhappy about this kind of initialization
return false;
}
}
FieldsShape::Arbitrary { offsets, .. } => {
for idx in 0..offsets.len() {
if !might_permit_raw_init_inner(
this.field(cx, idx),
cx,
init_kind,
inside_array,
) {
// We found a field that is unhappy with this kind of initialization.
return false;
}
}
}
}

if matches!(this.variants, Variants::Multiple { .. }) && init_kind == InitKind::Uninit {
// All uninit enums are automatically invalid, even if their discriminant includes all values.
return false;
}

true
}
}
68 changes: 0 additions & 68 deletions compiler/rustc_target/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1485,72 +1485,4 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {
Abi::Aggregate { sized } => sized && self.size.bytes() == 0,
}
}

/// Determines if this type permits "raw" initialization by just transmuting some
/// memory into an instance of `T`.
///
/// `init_kind` indicates if the memory is zero-initialized or left uninitialized.
///
/// This code is intentionally conservative, and will not detect
/// * zero init of an enum whose 0 variant does not allow zero initialization
/// * making uninitialized types who have a full valid range (ints, floats, raw pointers)
/// * Any form of invalid value being made inside an array (unless the value is uninhabited)
///
/// A strict form of these checks that uses const evaluation exists in
/// `rustc_const_eval::might_permit_raw_init`, and a tracking issue for making these checks
/// stricter is <https://github.com/rust-lang/rust/issues/66151>.
///
/// FIXME: Once all the conservatism is removed from here, and the checks are ran by default,
/// we can use the const evaluation checks always instead.
pub fn might_permit_raw_init<C>(self, cx: &C, init_kind: InitKind) -> bool
where
Self: Copy,
Ty: TyAbiInterface<'a, C>,
C: HasDataLayout,
{
let scalar_allows_raw_init = move |s: Scalar| -> bool {
match init_kind {
InitKind::Zero => {
// The range must contain 0.
s.valid_range(cx).contains(0)
}
InitKind::Uninit => {
// The range must include all values.
s.is_always_valid(cx)
}
}
};

// Check the ABI.
let valid = match self.abi {
Abi::Uninhabited => false, // definitely UB
Abi::Scalar(s) => scalar_allows_raw_init(s),
Abi::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2),
Abi::Vector { element: s, count } => count == 0 || scalar_allows_raw_init(s),
Abi::Aggregate { .. } => true, // Fields are checked below.
};
if !valid {
// This is definitely not okay.
return false;
}

// If we have not found an error yet, we need to recursively descend into fields.
match &self.fields {
FieldsShape::Primitive | FieldsShape::Union { .. } => {}
FieldsShape::Array { .. } => {
// FIXME(#66151): For now, we are conservative and do not check arrays by default.
}
FieldsShape::Arbitrary { offsets, .. } => {
for idx in 0..offsets.len() {
if !self.field(cx, idx).might_permit_raw_init(cx, init_kind) {
// We found a field that is unhappy with this kind of initialization.
return false;
}
}
}
}

// FIXME(#66151): For now, we are conservative and do not check `self.variants`.
true
}
}
Loading