首页 > 解决方案 > C++ 代码的 Rust 等效项,用于演示对 UAF 的保护

问题描述

我试图了解 Rust 的生命周期注释如何在不影响功能的情况下帮助捕获释放后使用 (UAF) 问题。这是我的代码:

use std::rc::Rc;

struct Foo {
    data: usize,
}

impl Foo {
    pub fn begin_foo(&self) {
        println!("Begin Foo");
    }

    pub fn end_foo(&self) {
        println!("End Foo");
    }
}

struct Bar {
    foo: Option<Foo>,
}

impl Bar {
    pub fn new(foo: Foo) -> Self {
        Bar {
            foo : Some(foo),
        }
    }

    pub fn get_foo(&self) -> Option<& Foo>
    {
        match &self.foo {
            Some(foo) => return Some(&foo),
            None => return None,
        }
    }
}

struct FooBar<'a> {
    bar : Option<Rc<Bar>>,
    foo : Option<&'a Foo>,
}

impl<'a> FooBar<'a> {
    pub fn new(bar : Option<Rc<Bar>>) -> Self {
        FooBar {
            bar : bar,
            foo : None,
        }
    }

    pub fn update_foo_1(&'a mut self)
    {
        if let Some(b) = &self.bar {
            self.foo = b.get_foo();
        }
    }

    // ERROR1
    /*
    pub fn update_foo_2(&mut self)
    {
        if let Some(b) = &self.bar {
            self.foo = b.get_foo();
        }
    }
    */

    pub fn invalidate_bar_1(&mut self) {
        self.bar.take();
    }

    // Inorder to drop the RC, we have to pass self and not &self, which consumes the object.
    pub fn invalidate_bar_2(self) {
        if let Some(bar) = &self.bar {
            println!("Strong ref count = {}", Rc::strong_count(bar));
            drop(self.bar.unwrap());
        }
    }

    pub fn use_foo(&self) {
        match self.foo {
            Some(foo) => println!("Foo.data = {}", foo.data),
            None => println!("Foo is None"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn works()
    {
        println!("Works...");
        let foo = Foo {data : 42};
        let bar = Bar::new(foo);

        let bar_rc = Rc::new(bar);
        assert_eq!(1, Rc::strong_count(&bar_rc));

        let mut foo_bar: FooBar = FooBar::new(Some(bar_rc));
        
        foo_bar.update_foo_1();
        foo_bar.use_foo();
        foo_bar.invalidate_bar_1();
        println!("Exiting works()...");
    }

    #[test]
    fn fails()
    {
        println!("Fails...");
        let foo = Foo {data : 42};
        let bar = Bar::new(foo);

        let bar_rc = Rc::new(bar);
        assert_eq!(1, Rc::strong_count(&bar_rc));

        let mut foo_bar: FooBar = FooBar::new(Some(bar_rc));
        
        foo_bar.update_foo_1();
        foo_bar.invalidate_bar_1();
        foo_bar.use_foo();
        println!("Exiting fails()...");
    }
}

要求是FooBar保留但也是避免多次调用Bar的参考。使参考悬空无效。FooBar::get_foo()FooBar::invalidate_bar_*()BarFoo

鉴于上述要求,以下调用序列应该是有效的:

foobar.update_foo();
foobar.use_foo();
foobar.invalidate_foo();

以下是无效的调用序列,必须在编译时捕获:

foobar.update_foo();
foobar.invalidate_foo();
foobar.use_foo();

但是我上面的代码并没有完全发生这种情况。我面临的挑战是:

  1. FooBar::update_foo_1()我被迫self用生命来注释&'a。否则我会收到以下错误:
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> src/lib.rs:52:26
   |
52 |         if let Some(b) = &self.bar {
   |                          ^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 50:5...
  --> src/lib.rs:50:5
   |
50 | /     pub fn update_foo_1(& mut self)
51 | |     {
52 | |         if let Some(b) = &self.bar {
53 | |             self.foo = b.get_foo();
54 | |         }
55 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/lib.rs:52:26
   |
52 |         if let Some(b) = &self.bar {
   |                          ^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 42:6...
  --> src/lib.rs:42:6
   |
42 | impl<'a> FooBar<'a> {
   |      ^^
note: ...so that the expression is assignable
  --> src/lib.rs:53:24
   |
53 |             self.foo = b.get_foo();
   |                        ^^^^^^^^^^^
   = note: expected `std::option::Option<&'a Foo>`
              found `std::option::Option<&Foo>`
  1. 当我使用&'a来解决上述错误时,我无法self在同一范围内进行任何其他需要独占引用的调用:
error[E0502]: cannot borrow `foo_bar` as immutable because it is also borrowed as mutable
   --> src/lib.rs:104:9
    |
103 |         foo_bar.update_foo_1();
    |         ------- mutable borrow occurs here
104 |         foo_bar.use_foo();
    |         ^^^^^^^
    |         |
    |         immutable borrow occurs here
    |         mutable borrow later used here

标签: rust

解决方案


通过调用它,您正在使 FooBar 成为一个自引用结构,这在安全 rust 中很难定义。

    pub fn update_foo_1(&'a mut self)
    {
        if let Some(b) = &self.bar {
            self.foo = b.get_foo();
        }
    }

编译器在这里正确给出错误:

error[E0502]: cannot borrow `foo_bar` as immutable because it is also borrowed as mutable
   --> src/lib.rs:104:9
    |
103 |         foo_bar.update_foo_1();
    |         ------- mutable borrow occurs here
104 |         foo_bar.use_foo();
    |         ^^^^^^^
    |         |
    |         immutable borrow occurs here
    |         mutable borrow later used here

因为如果它在更新调用之后确实允许任何不可变借用,则意味着此时没有可变借用,这也意味着在那之后您可以进行可变借用。

如果该bar字段的强计数为 1 并且您这样做了会发生什么:

drop(foo_bar.bar.take());

你会在 的foo领域有一个悬空的引用FooBar,这是 Rust 所禁止的。


推荐阅读