Dylan Falconer

Join 500+ game programmers getting weekly tips.

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:

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.

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

Thanks for reading!

If you found this useful, join the mailing list for more!


← Articles

Join 500+ game programmers getting weekly tips.