notes

Log | Files | Refs | README

segfault.md (3284B)


      1 # Segfault
      2 
      3 A segmentation fault (segfault) is a runtime error that occurs when a program
      4 tries to access a memory location it is not permitted to access — such as
      5 reading or writing outside its allocated memory bounds. The operating system
      6 catches this illegal access and terminates the offending process, sending a
      7 SIGSEGV signal on Unix-like systems or raising a STATUS_ACCESS_VIOLATION
      8 exception on Windows.
      9 
     10 ## How It Happens
     11 
     12 Program memory is divided into segments — text (instructions), data (global
     13 variables), stack (local variables), and heap (dynamically allocated memory). A
     14 segfault occurs when a reference falls outside the segment where a variable
     15 resides, or when a write is attempted to a read-only segment. The most common
     16 causes are: ​
     17 
     18 - Null pointer dereference — accessing memory at address 0x0
     19 
     20 - Buffer overflow — reading/writing past the end of an array
     21 
     22 - Dangling pointer — using a pointer to memory that has already been freed
     23 
     24 - Stack overflow — infinite recursion exhausting the stack
     25 
     26 ## Rust and Segfaults
     27 
     28 Rust's ownership and borrow checker system is specifically designed to eliminate
     29 segfaults in safe code at compile time. According to the Rust team, a Rust
     30 program can only segfault in two scenarios: you used unsafe code that violates
     31 memory safety guarantees, or the Rust compiler itself has a bug. ​
     32 
     33 ### Triggering a segfault with unsafe Rust
     34 
     35 The most direct way is dereferencing a raw null or invalid pointer inside an
     36 unsafe block:
     37 
     38 ```rust
     39 fn main() {
     40     // Dereference an invalid memory address — instant segfault
     41     unsafe { *(0x1 as *mut i32) = 1 };
     42 }
     43 ```
     44 
     45 This writes to memory address 0x1, which is not mapped, causing the OS to send
     46 SIGSEGV.
     47 
     48 ### Stack overflow via infinite recursion
     49 
     50 ```rust
     51 fn recurse() {
     52     recurse(); // infinite recursion → stack overflow → crash
     53 }
     54 
     55 fn main() {
     56     recurse();
     57 }
     58 ```
     59 
     60 Rust's runtime catches this and typically raises SIGABRT with a "thread has
     61 overflowed its stack" message rather than a raw SIGSEGV, but it is the same
     62 underlying mechanism.
     63 
     64 ### Writing to read-only memory via unsafe
     65 
     66 ```rust
     67 fn main() {
     68     let x: &str = "hello"; // stored in read-only memory
     69     let ptr = x.as_ptr() as *mut u8;
     70     unsafe {
     71         *ptr = b'H'; // writing to read-only segment → segfault
     72     }
     73 }
     74 ```
     75 
     76 ## Safe Rust vs. Unsafe Rust
     77 
     78 | Scenario                 | Safe Rust                             | `unsafe` Rust                        |
     79 | :----------------------- | :------------------------------------ | :----------------------------------- |
     80 | Null pointer dereference | Impossible — `Option<T>` used instead | Possible with raw pointers           |
     81 | Buffer overflow          | Panics with bounds check              | Possible with raw pointer arithmetic |
     82 | Dangling pointer         | Prevented by borrow checker           | Possible                             |
     83 | Stack overflow           | Handled gracefully (abort)            | Same behaviour                       |
     84 
     85 The key takeaway is that safe Rust **prevents segfaults by design** — the borrow
     86 checker enforces memory safety rules at compile time that languages like C/C++
     87 leave to the programmer. When you do need low-level control, `unsafe` blocks opt
     88 out of these guarantees and reintroduce the risk.