编译器通过禁止分配给借用的值来防止什么灾难?

What disaster does the compiler prevent by disallowing assigning to a borrowed value?

来自 Programming in Rust (PDF) 的示例:

#[derive(Debug)]
enum IntOrString {
    I(isize),
    S(String),
}

fn corrupt_enum() {
    let mut s = IntOrString::S(String::new());
    match s {
        IntOrString::I(_) => (),
        IntOrString::S(ref p) => {
            s = IntOrString::I(0xdeadbeef);
            // Now p is a &String, pointing at memory
            // that is an int of our choosing!
        }
    }
}

corrupt_enum();

编译器不允许这样做:

error[E0506]: cannot assign to `s` because it is borrowed
  --> src/main.rs:13:17
   |
12 |             IntOrString::S(ref p) => {
   |                            ----- borrow of `s` occurs here
13 |                 s = IntOrString::I(0xdeadbeef);
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `s` occurs here

但假设确实如此;

怎么样

Now p is a &String, pointing at memory that is an int of our choosing!

是坏事吗?

让我们为涉及的类型做一个内存布局。 IntOrString 将有一个字节来确定它是哪个变体(0 = 数字,1 = 字符串),后跟 4 个字节,这将是一个数字或地址的开头一组 UTF-8 字符。

让我们在 0x100 的内存中分配 s。变体位于 0x100,值位于 0x101、0x102、0x103、0x104。另外,假设值的内容是指针0xABCD;这是字符串的字节所在的位置。

使用匹配臂 IntOrString::S(ref p) 时,p 将设置为值 0x101 - 它是对值的引用,值从 0x101 开始。当你尝试使用p时,处理器会转到地址0x101,读取值(一个地址),然后从该地址读取数据。

如果编译器允许您在此时更改s,那么新数据的新字节将替换存储在0x101处的值.在示例中,存储在值中的 "address" 现在将指向任意位置 (0xDEADBEEF)。如果我们尝试使用 "string",我们将开始读取不太可能对应于 UTF-8 数据的内存字节。

None 这是学术性的,这种问题可能发生在格式良好的 C 程序中。在good情况下,程序会崩溃。在糟糕的情况下,您可能会在不应该的情况下读取程序中的数据。甚至可以注入 shellcode,然后让攻击者能够 运行 代码 他们 在您的程序中编写。


请注意,上面的内存布局非常简化,而实际String更大更复杂。