1

来自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

但假设确实如此;怎么回事

现在p&String, 指向我们选择的 int 内存!

是坏事吗?

4

1 回答 1

4

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

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

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

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

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


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

于 2016-03-25T19:15:55.613 回答