What is an example of Rust code that causes a segfault?
Asked Answered
D

6

11

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?

Darned answered 11/7, 2020 at 2:43 Comment(2)
Are you excluding unsafe code?Dubbin
You should really explicitly exclude unsafe code, or this is an extremely silly and obvious question.Sadiron
D
13

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.

Dubbin answered 11/7, 2020 at 3:17 Comment(2)
@Darned Additionally, if you're only looking for something that will dump core, then this would suit the job and probably produce the same result for the foreseeable future.Gaylagayle
As mentioned in the answer above this currently produces an "illegal instruction" (SIGILL) in release mode. If you actually need to generate a "segmentation fault" (SIGSEGV) this currently works for me: unsafe { *(0x1 as *mut i32) = 1 };Orazio
C
12

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);
    }
}

Playground

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.

Cartilage answered 11/7, 2020 at 7:32 Comment(2)
If anyone is seeking an intentional segfault, this is probably the safer option as well, since segfaults being UB can be inherently dangerous due to unknowable reasons. (And don't ask me why someone would WANT it, but anyways *copies code*)Tzar
Actually, using the nix crate seems even better: signal::raise(signal::Signal::SIGSEGV).unwrap(); (I believe Unix only though, sadly)Tzar
O
4

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}");
}

Playground

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.

Opossum answered 7/6, 2023 at 6:56 Comment(5)
This assumes you have procfs mounted, which you needn't do. I wouldn't say it's rust causing a segfault either. Rust is just writing to a file. It's not it's fault the kernel made it magical and it caused a segfault.Sadiron
This could be a security issue, so you needn't assume that procfs isn't mounted. Also, saying it's the kernel that's caused the segfault is somewhat misleading, because the actual segfault does happen in 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
The segfault doesn't happen in println!() afaik. I would assume it's raised here github.com/torvalds/linux/blob/master/fs/proc/inode.c#L340. I believe this fault could be captured by the file system and just discarded. That is, it's a kernel thing. All Rust knows when compiled is that it wanted to look up and write to a file handle. What happens when you write to the filehandle is determined by the filesystem, which is procfs. This segfault happens because procfs proxies a call to write to a file, to a write to memory without checking whether the kernel will allow it.Sadiron
And I'm not saying the behavior of allowing the fault to bubble is a bad (or good) idea, just that it was a design decision in the implementation of the filesystem which you opted to use. Rust doesn't protect you this class of deployment or resource errors.Sadiron
I'm sorry, but you've gone from misleading to obviously wrong: Try running the example code in the Playground. You'll see that the write to /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
G
3

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 {})()
    }
    

    Playground.

    This no longer produces UB.

  • Trying to create the never (!) type.

    #![feature(never_type)]
    union Erroneous {
         a: (),
         b: !,
    }
    
    fn main() {
         unsafe { Erroneous { a: () }.b }
    }
    

    Playground.

  • 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.
         }
    }
    

    Playground.

  • And last, you can cheat and just force it to produce a UD2 directly:

    #![feature(asm)]
    fn main() {
         unsafe {
             asm! {
                 "ud2"
             }
         };
    }
    

    Playground

    Or using llvm_asm! instead of asm!

    #![feature(llvm_asm)]
    fn main() {
         unsafe {
             llvm_asm! {
                 "ud2"
             }
         };
    }
    

    Playground.

Gaylagayle answered 20/7, 2020 at 18:21 Comment(0)
H
1
fn main() {
    let mut arr = [5; 1000000000000];
    arr[0] = 3;
    eprintln!("{:?}", &arr[0..1])
}

And playground

Produces stack overflow - Exited with signal 6 (SIGABRT)

Humphries answered 7/6, 2023 at 5:6 Comment(4)
It'd be nice if you could add a little bit of text why this causes a SEGV. Also, you're lacking a ;. (Otherwise: good point. Maybe also add an example with infinite recursion?)Opossum
Here's a playground link - play.rust-lang.org/…Pantry
This is not a seg fault, or is it?Pantry
Or rather, Rust prevents seg fault by terminating when out of stackPantry
W
0

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);
    }
}
Wieren answered 22/8, 2021 at 12:37 Comment(1)
This is unsafe code, which is fair enough since the OP didn't specify but he probably means is a segfault possible in safe rust code.Mischievous

© 2022 - 2024 — McMap. All rights reserved.