
Ode to the use-after-free: one vulnerable function, a thousand possibilities - ingve
https://scarybeastsecurity.blogspot.com/2017/05/ode-to-use-after-free-one-vulnerable.html
======
moosingin3space
I think the generalization of "use-after-free" to "use-after-invalidation" is
the most important part of this article, showing that it's more about object
"lifetimes" than simply a memory management flaw. This is one reason why we
can't consider "modern C++" to be memory-safe.

I wonder if this sort of vulnerability is possible in Rust, and if it is, is
there a way to use Rust's borrow checker to make it impossible?

~~~
tyoverby
> I think the generalization of "use-after-free" to "use-after-invalidation"
> is the most important

> I wonder if this sort of vulnerability is possible in Rust

Rusts borrow checker is designed for the more general case of "use-after-
invalidation" and `free` is treated as a simple invalidation of what happens
to be a heap allocated structure.

Interestingly, the borrow checker also prevents invalidations that are still
common in memory-safe languages such as iterator invalidation.

~~~
cousin_it
What? It's very easy to get use-after-invalidation in Rust. Destructors called
during unwinding see stuff in an invalid state. You can probably make a
language that prevents use-after-invalidation in safe code (e.g. mark all
accessible mutable references as "dirty" during unwinding, and require unsafe
code to "clean" them) but Rust isn't trying to do that AFAIK.

~~~
shepmaster
Could you provide an example of such code? I was under the impression that
certain things were disallowed _because_ destructors aren't allowed to see the
struct in an invalid state.

A common case where I see people trying to do this is when you have a struct
where you are trying to replace a member variable:

    
    
        struct Foo {
            thing: Vec<i32>,
        }
        
        impl Foo {
            fn something(&mut self) -> Vec<i32> {
                let temp = self.thing;
                // If we panicked between these two lines, then the struct would be in an undefined state
                self.thing = vec![1];
                temp
            }
        }
    

This code produces the error `cannot move out of borrowed content`. For those
curious, you normally would write this as

    
    
        use std::mem;
        
        impl Foo {
            fn something(&mut self) -> Vec<i32> {
                mem::replace(&mut self.thing, vec![1])
            }
        }

~~~
cousin_it
How about this?

    
    
        struct Foo { bar: Bar }
        struct Bar { message: &'static str }
    
        fn change_foo(foo: &mut Foo) {
            change_bar(&mut foo.bar);
        }
    
        fn change_bar(bar: &mut Bar) {
            bar.message = "Invalid";
            if true {
                panic!();
            }
            bar.message = "Valid";
        }
    
        impl Drop for Foo {
            fn drop(&mut self) {
                println!("{}", self.bar.message);
            }
        }
    
        fn main() {
            let mut foo = Foo { bar: Bar { message: "" } };
            change_foo(&mut foo);
        }
    

The destructor of a struct sees a broken invariant of a nested struct.

~~~
shepmaster
I see, I think you are using a different definition of "invalid" than I and
the grandparent are. Rust will not allow you to access _memory_ that is
invalid, but your own invariants can certainly be broken.

For what it's worth, the solution I've seen for this type of case is another
struct that is used to restore to an acceptable state:

    
    
        fn change_bar(bar: &mut Bar) {
            let mut restore = Restore(bar);
            restore.message = "Invalid";
            if true {
                panic!();
            }
            restore.message = "Valid";
            // Don't rollback on success
            std::mem::forget(restore); 
        }
        
        struct Restore<'a>(&'a mut Bar);
        
        // Put back to an acceptable state
        impl<'a> Drop for Restore<'a> {
            fn drop(&mut self) {
                self.0.message = "Restored to some state";
            }
        }
    
        // Sugar so we don't have to know about the wrapper
        impl<'a> std::ops::Deref for Restore<'a> {
            type Target = Bar;
            fn deref(&self) -> &Bar { self.0 }
        }
        
        // Sugar so we don't have to know about the wrapper
        impl<'a> std::ops::DerefMut for Restore<'a> {
            fn deref_mut(&mut self) -> &mut Bar { self.0 }
        }

------
ktRolster
A simple way to approach this problem in C, that will work in most cases, is
something like this:

    
    
       void safer_free(void **x) {
          if(x!=NULL && *x!=NULL) {
             free(*x);
          }
          *x = NULL;
       }
    

the key part being to set the original pointer to NULL so a future use will
cause an exception, not a vulnerability.

~~~
TheSoftwareGuy
Except this won't work when multiple pointers exist to the same data.. which
is arguably when this kind of stuff is most difficult

~~~
lossolo
If you have multiple pointers to the same data with different lifetimes you
already need some kind of reference counting or garbage collection.

~~~
pornel
Not necessarily. It's safe if lifetimes of the other pointers are guaranteed
to be shorter than the lifetime of the object.

~~~
lossolo
That's why I wrote "with different lifetimes".

