Use After Free & Dangling Pointer
Use After Free is a bug caused by reusing or re-referencing memory after it has been freed. This often happens through the use of a Dangling pointer, which is a pointer to memory which is no longer valid, usually because it has been freed, although it can also happen if a pointer is uninitialised or initialised with the wrong address. This can result in risks to confidentiality, integrity or availability depending on whether the bug corrupts valid data, causes the program to crash or leads to a write-what-where primitive allowing an attacker to execute arbitrary code (CWE-416).
A simple example is the code below:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define IBAN_SIZE 512
int main(void) {
char* attacker_iban = (char*)malloc(IBAN_SIZE);
strcpy(attacker_iban, "doesn't matter");
free(attacker_iban);
char* victim_iban = (char*)malloc(IBAN_SIZE);
strcpy(victim_iban, "BE79548627221233");
// An attacker can overwrite the victim's IBAN with his own
// by using the `attacker_iban` pointer after it has been freed
// because malloc returned the same memory address to `victim_iban`
// as it did to `attacker_iban`.
strcpy(attacker_iban, "FR3512739000402715325728I88");
printf("%s\n", victim_iban); // prints "FR3512739000402715325728I88"
free(victim_iban);
return EXIT_SUCCESS;
}
Detection and Debugging Tools
To detect this bug, it is possible to use static code analysis tools such as Clang Static Analyzer or Cppcheck (for the C language) or fuzzers such as AFL++ (for the C language), although these tools do not detect all occurrences of the problem.
In the event of a failure, it is therefore also necessary to have debugging tools such as GDB or Valgrind (for the C language), that can identify the instruction causing the problem.
A more drastic solution to address this bug is to use languages that automatically manage memory at compile time or that use a garbage collector.
Exercise
The exercise program contains the implementation of some functions and, unfortunately, it doesn’t work. Your goal is:
- to find the bug using debugging tools;
- and to fix it.
The Case of Rust
In Rust, this bug is not possible thanks to its Ownership feature. It allows Rust’s memory to be managed automatically during compilation so Use After Free bugs are prevented.
Here is an example of Rust code that shows this feature:
fn main() {
// The string "Hello World!" is allocated on the heap here
// and is owned by the variable `my_string`.
let my_string = String::from("Hello World!");
// The ownership of the string "Hello World!" is transferred to
// the `drop` function which will automatically deallocate
// the string from the heap when the function returns.
drop(my_string);
println!("{}", my_string); // Does not compile.
}
It is the equivalent of the following C code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
// The string "Hello World!" is allocated on the heap here
// and its reference is stored in the pointer `my_string`.
char *my_string = malloc(sizeof("Hello World!"));
strcpy(my_string, "Hello World!");
// The string "Hello World!" is manually deallocated from the heap here
// using the pointer `my_string`.
free(my_string);
printf("%s\n", my_string); // This will compile and print garbage.
return EXIT_SUCCESS;
}
This example cleary shows that Rust prevents Use After Free bugs. In reality, the Ownership feature of Rust is composed of many more rules that will guarantee that the program won’t have any memory-safety bug but the final goal is the same.