Why `double free` did not occurred?


#1
use std::mem;

fn main()
{
    let s = String::from("hello");
    let ptr = s.as_ptr();
    let handler = ptr as u64;
    let len = s.len();
    let capacity = s.capacity();
    mem::forget(s);
    
    let s_new = unsafe { String::from_raw_parts(ptr as *mut _, len, capacity) };
    let s_new2 = unsafe { String::from_raw_parts(ptr as *mut _, len, capacity) };
}

In the example above, I create a string and record it’s address and length, and then forget it. And two string objects are create through from_raw_parts method, which takes the ownership of the pointed memory space. So, here are two objects owning the the same piece of memory, why there’s no errors like double free occurred?

------------- Edit --------------
Add the missing mem::forget line.


#2

There’s actually a triple free since s is not forgotten. Due to lexical lifetimes all 3 are dropped at the end of main. You may not observe any memory violations in the way you’ve written the code. Instead, try dropping s and then printing one of the other strings - you should see an error about the string being invalid.


#3

yes, there’s a mistake in my code, there’s actually a triple free. Printing s_new and s_new2 after std::mem:drop s will cause invalid memory read, so there are errors occurred.
But my question is why there’s no error about double/tripple free?


#4

What error, and from who/what, are you expecting?


#5

For example, in C we have double_free error like:

 ✘ ⮀ ~/tmp ⮀ cat double_free.c
#include <stdlib.h>

int main() {
    char *p = malloc(10);
    free(p);
    free(p);
    return 0;
}
 ~/tmp ⮀ gcc double_free.c -o double_free
 ~/tmp ⮀ ./double_free
double_free(34098,0x7fff9f1f8340) malloc: *** error for object 0x7ff508400350: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
[1]    34098 abort      ./double_free
 ✘ ⮀ ~/tmp ⮀

#6

This looks like a checked debug variant of the libc malloc?

Ok, so you’re expecting an error from the allocator. Most allocators don’t check for these types of errors. Some have debug options that allow you to run them in a checked mode where they attempt to detect some classes of memory violations.

I think if you wanted to run Rust code through something like that (say because you have some unsafe code), I’d go with an address sanitizer. That should catch more classes of memory errors anyway.


#7

Really? I recompile the code with gcc double_free.c -o double_free -o4 and also get the double free error message.


#8

What platform is this?

Also, this isn’t a gcc thing so optimization level shouldn’t matter - this is the allocator code (dynamically linked) that’s barfing.


#9

I’m on OSX.
I’ve also tried this code on redhat, which is likely to produce a glibc error message like:

*** glibc detected *** ./double_free: double free or corruption (fasttop): 0x0000000000601010 ***
======= Backtrace: =========
/lib64/libc.so.6[0x7f8c8a5cf47f]
/lib64/libc.so.6(cfree+0x4b)[0x7f8c8a5cf8db]
./double_free[0x400500]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x7f8c8a57a994]
./double_free[0x400429]
======= Memory map: ========

#10

Try this with jemalloc which is what Rust uses by default. Alternatively, select the system allocator for the Rust code.

I don’t recall libc doing this detection by default but I might be misremembering.


#11

That’s exactly an allocator issue. I use jemalloc to run the program, it core-dumped on Linux without any error message and exit silently on OSX.

Thanks you very much!


#12

Note that what macOS provides is just a “best effort” attempt to detect double frees when it can. It’s not a guarantee. For example, if you allocate anything else after the first free, it may reuse the address of the freed buffer; if you then do a second free, the allocator will think you were trying to free the new allocation, so it won’t report an error.

There are debugging tools like Valgrind and ASAN that can more reliably detect errors, at the cost of performance.