*ฅ^•ﻌ•^ฅ* ✨✨  HWisnu's blog  ✨✨ о ฅ^•ﻌ•^ฅ

Don't chain together C sanitizers

Introduction

Consider this C code:

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
    char* buffer = (char*) malloc(sizeof(char) * 1024);
    if (buffer == NULL) {
        fprintf(stderr, "Error: Memory allocation failed\n");
        return 1;
    }

    sprintf(buffer, "%d", argc);
    printf("Number of arguments passed: %s\n", buffer);

    for (int i=0; i<argc; i++) {
        printf("Argument %d: %s\n", i, argv[i]);
    }
    
    // free(buffer); // not freeing memory on purpose!
    return 0;
}

Explanation of the code:

  1. Allocate memory and assign it to buffer variable.
  2. Simple check to see if there's an error when allocating memory.
  3. Take user input.
  4. Deliberately not freeing the memory.

Running the Code with Chained Sanitizers

Run this command in the terminal, with chained sanitizers:

gcc -ggdb -Wall -Werror -Wextra -fsanitize=address,undefined,leak -fno-omit-frame-pointer -o asan_test -O asan_test.c

then execute: ./asan_test my_program 12345

voila! the program runs without any error:

Number of arguments passed: 3
Argument 0: ./asan_test
Argument 1: my_program
Argument 2: 12345

Cool, right?!

Understanding the Issue

Not quite! If you review the code, you'll see that we didn't free the memory allocated to the buffer on purpose. So, what is happening? Does this mean the sanitizers don't work?

Modifying the Compile Flags

The mistake here is in the chaining operation of sanitizers. Instead of chaining them altogether, we need to run them separately. But who has time to compile every C program three times, right? I personally prefer the address sanitizer (ASAN) since it does the most comprehensive check compared to other sanitizers (undefined: UBSAN, leak: LSAN).

Now, let's modify our compile flags command to:

gcc -ggdb -Wall -Werror -Wextra -fsanitize=address -fno-omit-frame-pointer -o asan_test -O asan_test.c

Running the Code with ASAN

When run the same execute command, now we receive:

=================================================================
==50278==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1024 byte(s) in 1 object(s) allocated from:
    #0 0x7f65af2fb6e7 in malloc (/home/linuxbrew/.linuxbrew/lib/gcc/current/libasan.so.8+0xfb6e7)
    #1 0x562fe63221d3 in main /home/xxyyzz/Documents/programming/languages/c/misc/unsafe_me
mory/asan_test.c:7
    #2 0x7f65af046249  (/lib/x86_64-linux-gnu/libc.so.6+0x27249) (BuildId: 58254ca972028402bc40624f
81388d85ec95f70d)

SUMMARY: AddressSanitizer: 1024 byte(s) leaked in 1 allocation(s).

The address sanitizer works perfectly! It manages to detect the memory leak and even tells us the line where it leaks (line 7 in the asan_test.c file).

Why so many flags??

Also, if you're wondering why I use so many compiler flags, I also write Zig and Rust, and both of them are super strict. By applying those compiler flags, I managed to get my C programming in the same strictness to at least Zig level.

It's ackshually makes the C program I write really difficult to get memory errors. There are memory test challenges that I've completed, and using these compiler flags, none of the memory errors managed to slip through.

It's a good idea for anyone writing C to apply the proper comp flags! Thanks for reading, cheers!


Tip: you can make an alias of the compiler flag command, for me I use gcc_safest as an alias for all that comp flags and I'm good to go!

References

#c #low level #memory issue #memory safety #programming #sanitizer