-
Notifications
You must be signed in to change notification settings - Fork 363
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
safe-uninit
is unsound
#1265
Comments
Thank you for the report! The issue should be first reported to the developers of the crate, so that they would have the opportunity to issue a fix before the the advisory goes live: Once the upstream issue is fixed, or it is abundantly clear that it's not going to be fixed, we can issue an advisory. |
my understanding is that its api is fundamentally flawed and cant be properly implemented(compilers fault) |
This is quite interesting. While writing the crate at the time I have believed that any such logical contradictions would not be actually possible. Even though the About the code in the playground, notice that it outputs the I don't think that it is possible to fix this in the crate itself and it seems to me that it is really some compiler issue with the optimizations. I would expect the compiler to know that with any possible random value in the uninitialized value the condition in the playground example would still be The disassembly of optimized variant: |
I've re-read the docs for |
i feel MaybeUninit docs should be way clearer on this; black_box states explicitly that programs should not rely on it for correctness so if this works or not is an implementation detail and i fear it could be a no-op on some targets or codegen backends but still do the cursed optimizations at the same time |
r#"Note however, that black_box is only (and can only be) provided on a “best-effort” basis. The extent to which it can block optimisations may vary depending upon the platform and code-gen backend used. Programs cannot rely on black_box for correctness in any way."# |
no but i made it work https://github.com/Hezuikn/unsound |
The current behavior of the compiler and the reasons behind it are described here: https://www.ralfj.de/blog/2019/07/14/uninit.html So I'm afraid the approach taken by There has been a proposal to add a "freeze" operation to LLVM to set a region of memory as set to an arbitrary value, which would allow this crate to work, but AFAIK it hasn't happened even in the upstream LLVM, let alone in Rust. |
Something like this is most likely to be okay for the foreseeable future. In exchange you'd be giving up support for asmjs and wasm and any other target without an inline asm. fn safe_uninit() -> Self {
let mut uninit = MaybeUninit::uninit();
unsafe {
core::arch::asm!(
"/* initializing {uninit} */",
uninit = in(reg) &mut uninit,
options(preserves_flags),
);
uninit.assume_init()
}
} |
@dtolnay I though about trying this too but hadn't had the time today. Did it actually work out or is it just a suggestion? |
https://doc.rust-lang.org/nightly/unstable-book/language-features/asm-experimental-arch.html |
#![feature(asm_experimental_arch)]
let mut uninit = core::mem::MaybeUninit::uninit();
let trivial: u8 = unsafe {
core::arch::asm!(
"/* initializing {uninit} */",
uninit = in(local) &mut uninit,
options(preserves_flags),
);
uninit.assume_init()
}; seems to work just fine on wasm32-wasi |
sym::black_box => {
args[0].val.store(self, result);
// We need to "use" the argument in some way LLVM can't introspect, and on
// targets that support it we can typically leverage inline assembly to do
// this. LLVM's interpretation of inline assembly is that it's, well, a black
// box. This isn't the greatest implementation since it probably deoptimizes
// more than we want, but it's so far good enough.
crate::asm::inline_asm_call(
self,
"",
"r,~{memory}",
&[result.llval],
self.type_void(),
true,
false,
llvm::AsmDialect::Att,
&[span],
false,
None,
)
.unwrap_or_else(|| bug!("failed to generate inline asm call for `black_box`"));
// We have copied the value to `result` already.
return;
} rust/compiler/rustc_codegen_llvm/src/intrinsic.rs |
Anyway, I have run several (criterion) benchmarks to see if really using the black box solution has any benefits over using well-initialized memory. By black box I mean either The disassembly explains it. The compiler actually initializes this memory with something in each case. In the case of zero-ed value it is 0 (obviously) and in the case with safe_uninit this is... in a simple case it is effectively anything that already was in that register before, though it takes several instructions to move the data back and forth between the stack and the register. #![feature(bench_black_box)]
fn safe_uninit() -> u32 {
let mut uninit = std::mem::MaybeUninit::uninit();
unsafe {
core::arch::asm!(
"/* initializing {uninit} */",
uninit = in(reg) &mut uninit,
options(preserves_flags),
);
uninit.assume_init()
}
}
pub fn a() {
let i = safe_uninit();
std::hint::black_box(i);
}
pub fn b() {
let i = 0;
std::hint::black_box(i);
} example::a:
push rax
mov rax, rsp
mov eax, dword ptr [rsp]
mov dword ptr [rsp + 4], eax
lea rax, [rsp + 4]
pop rax
ret
example::b:
sub rsp, 4
mov dword ptr [rsp], 0
mov rax, rsp
add rsp, 4
ret |
The point is that possibly because of any kind of black box the micro-optimizations intended by I feel that |
@Hezuikn would you like to draft & send us a PR for |
re: fix - I would hope that @max-ym could put a patched version out but I guess we can draft a PR for an advisory regardless tho it sounds to me like patching seems unfeasible ? |
safe-uninit
is unsound
There is one potential patch that would make the API sound: fn safe_uninit() -> Self {
unsafe {
core::mem::zeroed()
}
} All the types which implement |
rust playground
the above example doesnt reproduce using the actual crate in question but its "safe" wrapper is literally just:
fn safe_uninit() -> Self { unsafe { MaybeUninit::uninit().assume_init() } }
The text was updated successfully, but these errors were encountered: