Skip to Content
Course content

59: Using AddressSanitizer

Click on the "Edit" button in the top corner of the screen to edit your slide content.

One of the most frustrating things about C is that it often lets you get away with murder. You can write past the end of an array, access memory you've already freed, or miscalculate a pointer offset, and your program might still run perfectly fine—until it suddenly crashes in production for no apparent reason. I've spent countless hours of my career staring at a segfault that only happens on Tuesdays, only to realize I had a one-off error in a loop three hundred lines of code away from the crash site.

That's why we use AddressSanitizer (ASan). It's essentially a "super-debugger" built into the compiler that instruments your memory accesses. Instead of hoping the OS catches your mistake with a segmentation fault, ASan catches it the exact millisecond it happens and tells you exactly which line caused it.

The invisible heap overflow

Take a look at this snippet. I'm trying to write a simple function that duplicates a string but converts it to uppercase. On the surface, it looks reasonable.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

char* uppercase_copy(const char* src) {
    int len = strlen(src);
    // We allocate space for the characters
    char* dest = malloc(len); 
    
    for (int i = 0; i < len; i++) {
        dest[i] = toupper((unsigned char)src[i]);
    }
    
    dest[len] = '\0'; // Null terminate the string
    return dest;
}

int main() {
    char* result = uppercase_copy("hello");
    printf("Result: %s\n", result);
    free(result);
    return 0;
}

If you compile and run this with a standard gcc main.c, it will probably print "Result: HELLO" and exit with a return code of 0. It "works." But it's actually broken. I've allocated len bytes, but I'm writing len + 1 bytes because of that null terminator. I'm corrupting a tiny slice of memory right after my allocation.

Catching the ghost with -fsanitize=address

To find this, you don't need a debugger or a bunch of print statements. You just need to add a specific flag during compilation and linking. Try this:

gcc -fsanitize=address -g main.c -o bug_hunt

The -fsanitize=address flag tells the compiler to add "shadow memory" tracking to every single memory access. I added -g so that ASan can give us actual line numbers instead of cryptic hex addresses. When you run this version, the program won't just exit quietly. It will scream at you with a massive red error report:

==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000015 at pc 0x000000000104...
WRITE of size 1 at 0x602000000015 thread T0
    #0 0x104567 in uppercase_copy main.c:14
    #1 0x1045a1 in main main.c:18

Look at the top of that report. heap-buffer-overflow. Then look at the stack trace: main.c:14. That points exactly to dest[len] = '\0';. ASan just saved me twenty minutes of guessing.

Accounting for the null terminator

The fix here is a classic C mistake: forgetting that strlen returns the length of the string excluding the null terminator. Since every C string needs that \0 to mark the end, we always need to allocate one extra byte.

char* uppercase_copy(const char* src) {
    int len = strlen(src);
    // Fix: Allocate len + 1 to make room for the null terminator
    char* dest = malloc(len + 1); 
    
    if (dest == NULL) return NULL; // Always check your mallocs!

    for (int i = 0; i < len; i++) {
        dest[i] = toupper((unsigned char)src[i]);
    }
    
    dest[len] = '\0'; 
    return dest;
}

If you recompile with ASan and run it again, the error disappears. Now the memory we're writing to is actually memory we own. While ASan does add some overhead to the execution speed and memory usage, it is an absolute requirement for my development workflow. I never ship C code that hasn't been "ASan-cleaned" first.




📋 Practical Task

Fixing the Use-After-Free in a Dynamic Array

You are given a program that manages a dynamic list of integers. However, the program has a critical memory bug: it attempts to access an element of the array after the array has been resized (reallocated), but it uses a stale pointer.

Your Task:

  1. Compile the provided code using -fsanitize=address -g.
  2. Run the program and analyze the ASan output to identify the exact line where the use-after-free occurs.
  3. Fix the code so that the program correctly handles the pointer after realloc is called.
  4. Verify the fix by running the program through ASan again to ensure no memory errors remain.
#include <stdio.h>
#include <stdlib.h>

int main() {
    int* numbers = malloc(3 * sizeof(int));
    numbers[0] = 10; numbers[1] = 20; numbers[2] = 30;

    int* old_ptr = numbers; // Keeping a reference to the original start

    // Expand the array to hold 6 integers
    int* temp = realloc(numbers, 6 * sizeof(int));
    if (temp == NULL) {
        free(numbers);
        return 1;
    }
    numbers = temp;

    numbers[3] = 40;
    numbers[4] = 50;
    numbers[5] = 60;

    // BUG: Using the old pointer after realloc might have moved the memory block
    printf("The first element via old pointer is: %d\n", old_ptr[0]);

    free(numbers);
    return 0;
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.