Tools for memory safety in C - ASan
The current discourse around memory safety focuses on languages rather than tools.
Tools already exist to catch memory errors.
There are multiple sanitisers available in Clang, GCC, and MSVC: Address, Memory, Thread, Undefined Behavior.
Today I’m going to show you how and why you want to use the Address Sanitiser (ASan).
By utilising this tool you will:
Prevent difficult to find bugs.
Create more robust software.
Unfortunately, it seems that most programmers are not aware of these tools. I didn’t know about them for the first 4 years of programming in C.
Ignoring ASan is like driving blindfolded: reckless and bound to crash.
Using ASan while developing will reduce the likelihood of a whole host of bugs.
Catch out of bounds access on heap, stack and globals.
Never have a use-after-free or double-free again.
Detect leaked memory.
If you have been bitten by these problems in the past, read on.
Catch Out-of-bounds errors
Consider the following C program:
#include <stdio.h>
int main(void) {
int arr[10];
for (int i = 0; i <= 10; i += 1) {
arr[i] = i;
}
printf("Done!\\n");
return 0;
}
The mistake is obvious due to the tiny surface area of the program.
Loop limits are often calculated based on some dynamic data that makes this error harder to catch.
This program will compile and seemingly execute with no issues.
> cl /Zi oob.c
> oob
Done!
When running it from the command line that “Done!” is printed, so all good, right?
If you compiled and ran this program, you would have noticed that it takes a second or two to exit.
Running it in a debugger shows an error when the program exits.
Let’s see what compiling with ASan turned on tells us when we run it.
> cl /Zi /fsanitize=address oob.c
> oob
A simple explanation of how ASan works
Okay, that’s a pretty crazy looking message. Let’s go over what ASan is.
This is in no way a comprehensive explanation.
Usually when your program allocates memory on the stack or the heap, it’s reasonably close together.
ASan spreads out allocations and adds a bunch of “poison bytes” surrounding them.
When one of these poison bytes is written to or read from, ASan reports that as an error.
The “shadow bytes” seen above is like a minimap of the poisoned bytes.
It’s how ASan knows which memory is poisoned and what kind of poison is there.
Never have a use-after-free or double-free again
Consider this simple C program:
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int *nums = malloc(32);
free(nums);
printf("%d\\n", nums[0]);
return 0;
}
When I compile and run this, I get 0.
> cl /Zi uaf.c
> uaf
0
There’s no guarantee that it will print the correct value, and perhaps the page that was allocated is no longer readable, crashing the program.
Compiled with ASan and I get a helpful error.
The first line: heap-use-after free … followed by a location uaf.c:7
You can dangle pointers all day long. Not that I advise that kind of rash memory allocation.
Detect memory leaks
For some reason this is not working on my Windows whether I compile with Clang or CL.
It’s disappointing, but I don’t get memory leaks in my own code anyway due to using custom memory allocators.
So, for this example I’ll be firing up the old WSL2 Ubuntu and using Clang.
Our program with a simple memory leak:
#include <stdlib.h>
int main(void) {
char *buf = malloc(512);
return 0;
}
Compiling and running the program:
$ clang -g -fsanitize=address leak.c -o leak
$ ./leak
ASan successfully caught this memory leak.
Tthis kind of memory leak can be a false positive as there’s no need to free memory when closing the program normally.
Perhaps using some kind of macro to turn on clean-up code when running ASan is called for.
Takeaways
ASan is simple to use. Just add the parameter to your compile command.
It will detect a whole host of memory errors so you can ship robust programs.
On my Windows machine it’s iffy with leaked memory. Test on Linux with Clang if cross-compilation is easy.
Thanks for reading!
If you found this useful, join the mailing list for more!