|
| 1 | +use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout}; |
| 2 | +use rustc_middle::ty::{ParamEnv, TyCtxt}; |
| 3 | +use rustc_session::Limit; |
| 4 | +use rustc_target::abi::{Abi, FieldsShape, InitKind, Scalar, Variants}; |
| 5 | + |
| 6 | +use crate::const_eval::CompileTimeInterpreter; |
| 7 | +use crate::interpret::{InterpCx, MemoryKind, OpTy}; |
| 8 | + |
| 9 | +/// Determines if this type permits "raw" initialization by just transmuting some memory into an |
| 10 | +/// instance of `T`. |
| 11 | +/// |
| 12 | +/// `init_kind` indicates if the memory is zero-initialized or left uninitialized. We assume |
| 13 | +/// uninitialized memory is mitigated by filling it with 0x01, which reduces the chance of causing |
| 14 | +/// LLVM UB. |
| 15 | +/// |
| 16 | +/// By default we check whether that operation would cause *LLVM UB*, i.e., whether the LLVM IR we |
| 17 | +/// generate has UB or not. This is a mitigation strategy, which is why we are okay with accepting |
| 18 | +/// Rust UB as long as there is no risk of miscompilations. The `strict_init_checks` can be set to |
| 19 | +/// do a full check against Rust UB instead (in which case we will also ignore the 0x01-filling and |
| 20 | +/// to the full uninit check). |
| 21 | +pub fn might_permit_raw_init<'tcx>( |
| 22 | + tcx: TyCtxt<'tcx>, |
| 23 | + ty: TyAndLayout<'tcx>, |
| 24 | + kind: InitKind, |
| 25 | +) -> bool { |
| 26 | + if tcx.sess.opts.unstable_opts.strict_init_checks { |
| 27 | + might_permit_raw_init_strict(ty, tcx, kind) |
| 28 | + } else { |
| 29 | + let layout_cx = LayoutCx { tcx, param_env: ParamEnv::reveal_all() }; |
| 30 | + might_permit_raw_init_lax(ty, &layout_cx, kind) |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +/// Implements the 'strict' version of the `might_permit_raw_init` checks; see that function for |
| 35 | +/// details. |
| 36 | +fn might_permit_raw_init_strict<'tcx>( |
| 37 | + ty: TyAndLayout<'tcx>, |
| 38 | + tcx: TyCtxt<'tcx>, |
| 39 | + kind: InitKind, |
| 40 | +) -> bool { |
| 41 | + let machine = CompileTimeInterpreter::new( |
| 42 | + Limit::new(0), |
| 43 | + /*can_access_statics:*/ false, |
| 44 | + /*check_alignment:*/ true, |
| 45 | + ); |
| 46 | + |
| 47 | + let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine); |
| 48 | + |
| 49 | + let allocated = cx |
| 50 | + .allocate(ty, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap)) |
| 51 | + .expect("OOM: failed to allocate for uninit check"); |
| 52 | + |
| 53 | + if kind == InitKind::Zero { |
| 54 | + cx.write_bytes_ptr( |
| 55 | + allocated.ptr, |
| 56 | + std::iter::repeat(0_u8).take(ty.layout.size().bytes_usize()), |
| 57 | + ) |
| 58 | + .expect("failed to write bytes for zero valid check"); |
| 59 | + } |
| 60 | + |
| 61 | + let ot: OpTy<'_, _> = allocated.into(); |
| 62 | + |
| 63 | + // Assume that if it failed, it's a validation failure. |
| 64 | + // This does *not* actually check that references are dereferenceable, but since all types that |
| 65 | + // require dereferenceability also require non-null, we don't actually get any false negatives |
| 66 | + // due to this. |
| 67 | + cx.validate_operand(&ot).is_ok() |
| 68 | +} |
| 69 | + |
| 70 | +/// Implements the 'lax' (default) version of the `might_permit_raw_init` checks; see that function for |
| 71 | +/// details. |
| 72 | +fn might_permit_raw_init_lax<'tcx>( |
| 73 | + this: TyAndLayout<'tcx>, |
| 74 | + cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, |
| 75 | + init_kind: InitKind, |
| 76 | +) -> bool { |
| 77 | + let scalar_allows_raw_init = move |s: Scalar| -> bool { |
| 78 | + match init_kind { |
| 79 | + InitKind::Zero => { |
| 80 | + // The range must contain 0. |
| 81 | + s.valid_range(cx).contains(0) |
| 82 | + } |
| 83 | + InitKind::UninitMitigated0x01Fill => { |
| 84 | + // The range must include an 0x01-filled buffer. |
| 85 | + let mut val: u128 = 0x01; |
| 86 | + for _ in 1..s.size(cx).bytes() { |
| 87 | + // For sizes >1, repeat the 0x01. |
| 88 | + val = (val << 8) | 0x01; |
| 89 | + } |
| 90 | + s.valid_range(cx).contains(val) |
| 91 | + } |
| 92 | + } |
| 93 | + }; |
| 94 | + |
| 95 | + // Check the ABI. |
| 96 | + let valid = match this.abi { |
| 97 | + Abi::Uninhabited => false, // definitely UB |
| 98 | + Abi::Scalar(s) => scalar_allows_raw_init(s), |
| 99 | + Abi::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2), |
| 100 | + Abi::Vector { element: s, count } => count == 0 || scalar_allows_raw_init(s), |
| 101 | + Abi::Aggregate { .. } => true, // Fields are checked below. |
| 102 | + }; |
| 103 | + if !valid { |
| 104 | + // This is definitely not okay. |
| 105 | + return false; |
| 106 | + } |
| 107 | + |
| 108 | + // Special magic check for references and boxes (i.e., special pointer types). |
| 109 | + if let Some(pointee) = this.ty.builtin_deref(false) { |
| 110 | + let pointee = cx.layout_of(pointee.ty).expect("need to be able to compute layouts"); |
| 111 | + // We need to ensure that the LLVM attributes `aligned` and `dereferenceable(size)` are satisfied. |
| 112 | + if pointee.align.abi.bytes() > 1 { |
| 113 | + // 0x01-filling is not aligned. |
| 114 | + return false; |
| 115 | + } |
| 116 | + if pointee.size.bytes() > 0 { |
| 117 | + // A 'fake' integer pointer is not sufficiently dereferenceable. |
| 118 | + return false; |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + // If we have not found an error yet, we need to recursively descend into fields. |
| 123 | + match &this.fields { |
| 124 | + FieldsShape::Primitive | FieldsShape::Union { .. } => {} |
| 125 | + FieldsShape::Array { .. } => { |
| 126 | + // Arrays never have scalar layout in LLVM, so if the array is not actually |
| 127 | + // accessed, there is no LLVM UB -- therefore we can skip this. |
| 128 | + } |
| 129 | + FieldsShape::Arbitrary { offsets, .. } => { |
| 130 | + for idx in 0..offsets.len() { |
| 131 | + if !might_permit_raw_init_lax(this.field(cx, idx), cx, init_kind) { |
| 132 | + // We found a field that is unhappy with this kind of initialization. |
| 133 | + return false; |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + match &this.variants { |
| 140 | + Variants::Single { .. } => { |
| 141 | + // All fields of this single variant have already been checked above, there is nothing |
| 142 | + // else to do. |
| 143 | + } |
| 144 | + Variants::Multiple { .. } => { |
| 145 | + // We cannot tell LLVM anything about the details of this multi-variant layout, so |
| 146 | + // invalid values "hidden" inside the variant cannot cause LLVM trouble. |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + true |
| 151 | +} |
0 commit comments