-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
panic-on-uninit: adjust checks to 0x01-filling #101061
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
151 changes: 151 additions & 0 deletions
151
compiler/rustc_const_eval/src/util/might_permit_raw_init.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout}; | ||
use rustc_middle::ty::{ParamEnv, TyCtxt}; | ||
use rustc_session::Limit; | ||
use rustc_target::abi::{Abi, FieldsShape, InitKind, Scalar, Variants}; | ||
|
||
use crate::const_eval::CompileTimeInterpreter; | ||
use crate::interpret::{InterpCx, MemoryKind, OpTy}; | ||
|
||
/// 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. We assume | ||
/// uninitialized memory is mitigated by filling it with 0x01, which reduces the chance of causing | ||
/// LLVM UB. | ||
/// | ||
/// By default we check whether that operation would cause *LLVM UB*, i.e., whether the LLVM IR we | ||
/// generate has UB or not. This is a mitigation strategy, which is why we are okay with accepting | ||
/// Rust UB as long as there is no risk of miscompilations. The `strict_init_checks` can be set to | ||
/// do a full check against Rust UB instead (in which case we will also ignore the 0x01-filling and | ||
/// to the full uninit check). | ||
pub fn might_permit_raw_init<'tcx>( | ||
tcx: TyCtxt<'tcx>, | ||
ty: TyAndLayout<'tcx>, | ||
kind: InitKind, | ||
) -> bool { | ||
if tcx.sess.opts.unstable_opts.strict_init_checks { | ||
might_permit_raw_init_strict(ty, tcx, kind) | ||
} else { | ||
let layout_cx = LayoutCx { tcx, param_env: ParamEnv::reveal_all() }; | ||
might_permit_raw_init_lax(ty, &layout_cx, kind) | ||
} | ||
} | ||
|
||
/// Implements the 'strict' version of the `might_permit_raw_init` checks; see that function for | ||
/// details. | ||
fn might_permit_raw_init_strict<'tcx>( | ||
ty: TyAndLayout<'tcx>, | ||
tcx: TyCtxt<'tcx>, | ||
kind: InitKind, | ||
) -> bool { | ||
let machine = CompileTimeInterpreter::new( | ||
Limit::new(0), | ||
/*can_access_statics:*/ false, | ||
/*check_alignment:*/ true, | ||
); | ||
|
||
let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine); | ||
|
||
let allocated = cx | ||
.allocate(ty, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap)) | ||
.expect("OOM: failed to allocate for uninit check"); | ||
|
||
if kind == InitKind::Zero { | ||
cx.write_bytes_ptr( | ||
allocated.ptr, | ||
std::iter::repeat(0_u8).take(ty.layout.size().bytes_usize()), | ||
) | ||
.expect("failed to write bytes for zero valid check"); | ||
} | ||
|
||
let ot: OpTy<'_, _> = allocated.into(); | ||
|
||
// Assume that if it failed, it's a validation failure. | ||
// This does *not* actually check that references are dereferenceable, but since all types that | ||
// require dereferenceability also require non-null, we don't actually get any false negatives | ||
// due to this. | ||
cx.validate_operand(&ot).is_ok() | ||
} | ||
|
||
/// Implements the 'lax' (default) version of the `might_permit_raw_init` checks; see that function for | ||
/// details. | ||
fn might_permit_raw_init_lax<'tcx>( | ||
this: TyAndLayout<'tcx>, | ||
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, | ||
init_kind: InitKind, | ||
) -> bool { | ||
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::UninitMitigated0x01Fill => { | ||
// The range must include an 0x01-filled buffer. | ||
let mut val: u128 = 0x01; | ||
for _ in 1..s.size(cx).bytes() { | ||
// For sizes >1, repeat the 0x01. | ||
val = (val << 8) | 0x01; | ||
} | ||
s.valid_range(cx).contains(val) | ||
} | ||
} | ||
}; | ||
|
||
// 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; | ||
} | ||
|
||
// Special magic check for references and boxes (i.e., special pointer types). | ||
if let Some(pointee) = this.ty.builtin_deref(false) { | ||
let pointee = cx.layout_of(pointee.ty).expect("need to be able to compute layouts"); | ||
// We need to ensure that the LLVM attributes `aligned` and `dereferenceable(size)` are satisfied. | ||
if pointee.align.abi.bytes() > 1 { | ||
// 0x01-filling is not aligned. | ||
return false; | ||
} | ||
if pointee.size.bytes() > 0 { | ||
// A 'fake' integer pointer is not sufficiently dereferenceable. | ||
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 { .. } => { | ||
// Arrays never have scalar layout in LLVM, so if the array is not actually | ||
// accessed, there is no LLVM UB -- therefore we can skip this. | ||
} | ||
FieldsShape::Arbitrary { offsets, .. } => { | ||
for idx in 0..offsets.len() { | ||
if !might_permit_raw_init_lax(this.field(cx, idx), cx, init_kind) { | ||
// We found a field that is unhappy with this kind of initialization. | ||
return false; | ||
} | ||
} | ||
} | ||
} | ||
|
||
match &this.variants { | ||
Variants::Single { .. } => { | ||
// All fields of this single variant have already been checked above, there is nothing | ||
// else to do. | ||
} | ||
Variants::Multiple { .. } => { | ||
// We cannot tell LLVM anything about the details of this multi-variant layout, so | ||
// invalid values "hidden" inside the variant cannot cause LLVM trouble. | ||
} | ||
} | ||
|
||
true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we note references here? 0x1-filling is non-null, but still not dereferenceable (as mentioned) and also not aligned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I forgot about alignment... it would be handled seamlessly here if we used
0x00..align
as the niche, but right now we only have0x00..0x01
so we'd not complain about e.g.&i32
.IMO the best way to fix that is to make the niche bigger...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear to me why the niche matters, when a 0x1-filled reference means it has pointer 0x0101_0101_0101_0101.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to extend the niche of references from the current
0x00..0x01
to0x00..align
in the future, which would produce invalid values even with0x01
filling.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might_permit_raw_init uses the same information that is used for niches to determine whether a given value is valid for a given type.
Currently, that information says that 0x0101_0101_0101_0101 is valid for
&i32
, which leads to might_permit_raw_init allowing&i32
to be left uninit with 0x01-filling.If we extend the niche to take into account alignment, that information will say that 0x0101_0101_0101_0101 is not valid for
&i32
, and hence trying to create a 0x01-filled&i32
would be rejected (as it should).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, but those extended niche values are still far from the filled value we're talking about, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we had a way to represent a niche that is "all the unaligned values", then that would be sufficient for this check -- 0x0101_0101_0101_0101 would be in the niche and hence disallowed for
&i32
.But indeed we cannot even represent such niches, so contrary to what I said earlier we cannot rely on niches to detect the trouble around references.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, my last reply was having only seen what @Nilstrieb wrote - I guess GitHub failed to dynamically load @RalfJung's replies - but I think we're on the same page now.
So, is this something we should still worry about for LLVM UB in this context? We do tell LLVM about
align
, and their docs say, "If the pointer value does not have the specified alignment, poison value is returned or passed instead." But AIUI that's not UB until it's used in a particular way, so is it still a problem here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rust/compiler/rustc_middle/src/ty/layout.rs
Lines 3570 to 3584 in a6ab54b
This? It's in my #99389 PR where we allowed 1-aligned references with no
dereferencable
(inside arrays only)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cuviper poison value basically means uninit memory, which the 0x01-filling is intended to avoid, so I am leaning towards saying that this is LLVM UB we should avoid. (In particular,
align noundef
would make it insta-UB -- and that is the combination of attributes we want.)@5225225 yes, that one, thanks!