Search code examples
c++memory-leaksllvmsanitizerdynamic-analysis

Why LLVM's leak sanitizer not working when using with other sanitizers enabled


I was trying to find a memory leak from a simple program:

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

void parse(const char* input) {
    // Goal: parse out a string between brackets
    // (e.g. "   [target string]" -> "target string")

    char *mutable_copy = strdup(input);

    // Find open bracket
    char *open_bracket = strchr(mutable_copy, '[');
    if (open_bracket == NULL) {
        printf("Malformed input!\n");
        free(mutable_copy);
        return;
    }

    // Make the output string start after the open bracket
    char *parsed = open_bracket + 1;

    // Make sure there is at least one character in brackets
    if (parsed[0] == '\0') {
        printf("There should be at least one character in brackets!\n");
        free(mutable_copy);
        return;
    }

    // Find the close bracket
    char *close_bracket = strchr(parsed, ']');
    if (close_bracket == NULL) {
        printf("Malformed input!\n");
        return;
    }

    // Replace the close bracket with a null terminator to end the parsed
    // string there
    *close_bracket = '\0';

    printf("Parsed string: %s\n", parsed);
    free(mutable_copy);
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: %s <string to parse>\n", argv[0]);
        return 1;
    }

    parse(argv[1]);

    return 0;
}

with the following command: clang -g -O0 -Wall -Wextra -std=gnu99 -fsanitize=address,leak -o 3-bracket-parser 3-bracket-parser.c

It's obvious that in this program, if close_bracket == NULL is true, then the program returns without free mutable_copy, so there is a memory leak.

However, when I run this command, it reports no error and there is no output from my sanitizers. I tried again with only leak sanitizer enabled, and this time it works:

=================================================================
==19699==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 17 byte(s) in 1 object(s) allocated from:
    #0 0x10f77ab48 in wrap_malloc+0x58 (libclang_rt.lsan_osx_dynamic.dylib:x86_64h+0x9b48)
    #1 0x7fff202b88d1 in strdup+0x1f (libsystem_c.dylib:x86_64+0x578d1)
    #2 0x10f763db4 in parse 3-bracket-parser.c:10
    #3 0x10f763edd in main 3-bracket-parser.c:51
    #4 0x7fff203a7f3c in start+0x0 (libdyld.dylib:x86_64+0x15f3c)

SUMMARY: LeakSanitizer: 17 byte(s) leaked in 1 allocation(s).

I was wondering why leak sanitizer isn't working when other sanitizers are enabled.

My clang version is Homebrew clang version 12.0.1, and my OS is macOS Big Sur 11.5.2


Solution

  • Problem solved. It turns out that on OS X, lsan is integrated into asan, and one must manually use ASAN_OPTIONS=detect_leak=1 to enable lsan when using asan. It seems that if one uses -fsanitize=address,leak, clang thinks that this leak parameter is redundant since address sanitizer already includes leak sanitizer. If you look closely to the linking command, you may find out that ld only links asan dylib when both sanitizers are enabled, and you need to add ASAN_OPTIONS to enable lsan. However, if you use leak sanitizer standalone, so there is no need to use ASAN_OPTIONS to specify you want lsan enabled as well, and lsan works fine this way.