I Googled some segfault examples in Rust, but none of crash now. Is Rust able to prevent all segfaults now? Is there a simple demo that can cause a segfault?
If unsafe
code is allowed, then:
fn main() {
unsafe { std::ptr::null_mut::<i32>().write(42) };
}
results in:
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 1.37s
Running `target/debug/playground`
timeout: the monitored command dumped core
/playground/tools/entrypoint.sh: line 11: 7 Segmentation fault timeout --signal=KILL ${timeout} "$@"
as seen on the playground.
Any situation that would trigger a segfault would require invoking undefined behavior at some point. The compiler is allowed to optimize out code or otherwise exploit the fact that undefined behavior should never occur, so it's very hard to guarantee that some code will segfault. The compiler is well within its rights to make the above program run without triggering a segfault.
As an example, the code above when compiled in release mode results in an "Illegal instruction" instead.
If unsafe
code is not allowed, see How does Rust guarantee memory safety and prevent segfaults? for how Rust can guarantee it doesn't happen as long as its memory safety invariants aren't violated (which could only happen in unsafe
code).
Don't use unsafe code if you can avoid it.
unsafe { *(0x1 as *mut i32) = 1 };
–
Orazio Strictly speaking, it is always possible to trick a program into thinking that it had a segmentation fault, since this is a signal sent by the OS:
use libc::kill;
use std::process;
fn main() {
unsafe {
// First SIGSEGV will be consumed by Rust runtime
// (see https://users.rust-lang.org/t/is-sigsegv-handled-by-rust-runtime/45680)...
kill(process::id() as i32, libc::SIGSEGV);
// ...but the second will crash the program, as expected
kill(process::id() as i32, libc::SIGSEGV);
}
}
This is not really an answer to your question, since that's not a "real" segmentation fault, but taking the question literally - Rust program can still end with a "segmentation fault" error, and here's a case which reliably triggers it.
signal::raise(signal::Signal::SIGSEGV).unwrap();
(I believe Unix only though, sadly) –
Tzar In safe Rust, you can deliberately mess up the memory of your own process via OS facilities.
use std::io::Write;
use std::io::Seek;
fn main() {
let x = 42;
let y = &x;
// Delete the next few lines and everything is good.
let mut f = std::fs::OpenOptions::new()
.write(true)
.open("/proc/self/mem").expect("welp");
// Turn y into a nullptr
f.seek(std::io::SeekFrom::Start(&y as *const _ as u64)).expect("oof");
f.write(&0usize.to_ne_bytes()).expect("darn");
println!("{y}");
}
While this has some duuuuh-factor, the general technique has made some waves. It was even considered whether this is an unsoundness, but ultimately mostly rejected. (Argument being that Rust doesn't protect against machine weirdnesses, be it bitflips or the OS messing with process memory.)
Also notable: This bug.
This answer also used to contain
fn main() {
main();
}
as an example of a SIGSEGV that is safe, but this isn't reproducible anymore.
println!("{y}")
nowhere near any kernel code. In any case, all we can say here in comments has been discussed in more detail in the link I provided. (See the Wrapup section and the bits about "environmental model"?) –
Opossum /proc/self/mem
succeeds and it'll even execute the println!("We're still good here");
without any problems. Only after, the segfault happens, in the part where it loads what's pointed to by y
, and the kernel can do precisely nothing about it. (Note that I was also misleading, the segv does indeed not happen in the write(1, "whatever is in y")
issued by println
, but in the Rust code before that, i.e. in the format!("{y}")
part.) –
Opossum If you're looking more generally for something that will dump core, and not specifically cause a segfault, there is another option which is to cause the compiler to emit an UD2
instruction or equivalent. There's a few things which can produce this:
An empty loop without any side effects is UB because of LLVM optimizations:fn main() { (|| loop {})() }
This no longer produces UB.
Trying to create the never (
!
) type.#![feature(never_type)] union Erroneous { a: (), b: !, } fn main() { unsafe { Erroneous { a: () }.b } }
Or also trying to use (in this case match on) an enum with no variants:
#[derive(Clone, Copy)] enum Uninhabited {} union Erroneous { a: (), b: Uninhabited, } fn main() { match unsafe { Erroneous { a: () }.b } { // Nothing to match on. } }
And last, you can cheat and just force it to produce a UD2 directly:
#![feature(asm)] fn main() { unsafe { asm! { "ud2" } }; }
Or using
llvm_asm!
instead ofasm!
#![feature(llvm_asm)] fn main() { unsafe { llvm_asm! { "ud2" } }; }
fn main() {
let mut arr = [5; 1000000000000];
arr[0] = 3;
eprintln!("{:?}", &arr[0..1])
}
And playground
Produces stack overflow - Exited with signal 6 (SIGABRT)
;
. (Otherwise: good point. Maybe also add an example with infinite recursion?) –
Opossum NULL pointer can cause segfault in both C and Rust.
union Foo<'a>{
a:i32,
s:&'a str,
}
fn main() {
let mut a = Foo{s:"fghgf"};
a.a = 0;
unsafe {
print!("{:?}", a.s);
}
}
© 2022 - 2024 — McMap. All rights reserved.
unsafe
code? – Dubbin