当我写入随机内存地址时会发生什么?

What happens when I write to a random memory address?

extern crate core;
use core::ops::{Deref, DerefMut};

struct MutPtr<T>{
    ptr: *mut T
}
impl<T> MutPtr<T>{
    fn new(value: &mut T) -> MutPtr<T>{
        MutPtr{ptr: value} 
    }
}
impl<T> Deref for MutPtr<T>{
    type Target = T;
    fn deref(&self) -> &T{
        unsafe{
            &(*self.ptr)
        }
    }
}
impl<T> DerefMut for MutPtr<T>{
    fn deref_mut(&mut self) -> &mut T{
        unsafe{
            &mut (*self.ptr)
        }
    }
}
struct Bar{
    v: i32
}

fn error()-> MutPtr<Bar> {
    let mut b = Bar{v:42};
    let ptr_b =  MutPtr::new(&mut b);
    ptr_b
}

fn main(){
    let mut b      = Bar{v:42};
    let mut ptr_b  = MutPtr::new(&mut b);
    let mut ptr_b1 = MutPtr::new(&mut b);

    ptr_b.v = 10;
    println!("{}",b.v);
    ptr_b1.v = 21;
    println!("{}",b.v);

    let mut err = error();
    println!("{}",err.v);
    err.v = 42; // what happens here?
    println!("{}",err.v);
}

在第 49 行,我将写入某个内存地址

err.v = 42;

我知道这很糟糕,但我想知道到底发生了什么?起初我预计它会崩溃,但后来我希望我能够更改 err.v 地址处的值。但是写没有做任何事情。

内存好像被写保护了?

我只是 "lucky" 写入没有改变任何东西吗?

Was I just "lucky" that the write didn't change anything?

是的,很幸运。

写入随机内存 undefined behaviour: the compiler assumes it never happens and optimises assuming this. If it does occur there's no limit or guarantees one can make about the resulting behaviour. E.g. it could change the return address 在下一个函数调用结束时使用,使 CPU 跳转到某些 "random" 内存。这通常真的很糟糕,这样的事情很可能是一个可利用的安全漏洞。

在您所写的情况下,没有可怕的程序死亡可能是因为 err 点位于堆栈下方,程序在写入发生时未使用它。如果在使用堆栈的那个区域时发生写入,则很容易发生将指针修改为指向无意义的事情:

use std::mem;

#[inline(never)]
fn bad() -> &'static mut u32 {
    let mut x = 0u32;
    unsafe { mem::transmute(&mut x) }
}

#[inline(never)]
fn innocent(x: &mut u32) {
    println!("{:p}", &*x);
    *x = 0xDEADBEEF;

    println!("{:p}", x);
    *x = 0;
}

fn main() {
    let ptr = bad();
    innocent(ptr);
}

playpen-O2 上,目前,它打印:

0x7fff03dbae84
0xdeadbeef03dbae84
playpen: application terminated abnormally with signal 4 (Illegal instruction)

第一行是x的实际值。下一行是 *x = 0xDEADBEEF; 之后的 x... 也就是说,写入是直接写入存储 x 本身的堆栈部分,将上半部分更改为 0xDEADBEEF .此时,x 是一个无意义的值,因此 *x = 0 会导致段错误(表现为中止,因为 Rust 默认覆盖了一些信号处理程序)。

我说 "at the moment" 因为程序的行为对确切的编译器 versions/optimisation levels/source 代码非常敏感,例如更改第一个 print 以删除 &* 会使程序打印 0x7fff03dbae84 两次然后中止(可能是因为正在修改 return 地址而不是 x) .

如果攻击者对写入无效指针的内容有任何控制,他们 may/are 可能能够修改写入指针以导致程序跳转到 piece of shellcode and pwn your application. I saw likely because even the smallest "unexploitable" problems证明是可以利用的。