6

When I compile this simple test program I get the obvious leak report from address sanitizer, but when I compile the same program but with a infinite loop, and break it emitting SIGINT I don't get any output.

Checking asm output, the malloc is not optimized away (if this is possible at all)

Is this the expected behavior of address sanitizer? I don't encounter this problem in other developments.

Working example:

#include <stdlib.h>
int main(void)
{
    char *a = malloc(1024);
    return 1;
}

Not working (kill with SIGINT):

#include <stdlib.h>
int main(void)
{
    char *a = malloc(1024);
    for(;;);
    return 1;
}

compile: gcc test.c -o test -fsanitize=address

I encounter this problem in a full programm but I reduced it to this minimal example.

leiyc
  • 903
  • 11
  • 23
Bungow
  • 420
  • 5
  • 11
  • 1
    Actually, it's the expected behavior. The reason is that otherwise you'd be guaranteed to get a bunch of false-positives: the more of them the bigger your app. An example why: suppose in your code, right after the cycle you had a `free(a)`. So technically, no leak in your code. However upon SIGINTing the app you'd get an invalid leak report, because the memory was not freed by the time you killed the app. Basically, SIGINT/SIGTERM means, no cleanups/destructors will get run except those you explicitly bound to the signal. – Hi-Angel Sep 17 '20 at 09:49

3 Answers3

7

I tried many ways, with exit() and abort() calls, this works:

#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <setjmp.h>

jmp_buf jmpbuf;
void handler (int signum) {    
        printf("handler %d \n", signum);
        // we jump from here to main()
        // and then call return
        longjmp(jmpbuf, 1);
}

int main(int argc, char *argv[])
{
    if (setjmp(jmpbuf)) { 
        // we are in signal context here
        return 2;
    }
    signal(SIGINT, handler);
    signal(SIGTERM, handler);

    char *a = malloc(1024);
    while (argc - 1);
    return 1;
}

Results in:

> gcc file.c -fsanitize=address && timeout 1 ./a.out arg
handler 15 

=================================================================
==12970==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1024 byte(s) in 1 object(s) allocated from:
    #0 0x7f4798c9bd99 in __interceptor_malloc /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cc:86
    #1 0x5569e64e0acd in main (/tmp/a.out+0xacd)
    #2 0x7f479881206a in __libc_start_main (/usr/lib/libc.so.6+0x2306a)

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

I guess that the address sanitizer function are executed after main returns.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • 1
    I guess you are right, but I'm just surprised leak sanitizer not running when calling `exit()`; I accept your answer for providing a way to force output with `setjmp`. – Bungow Aug 22 '18 at 11:42
  • 1
    @Bungow most liekly it doesn’t work because call to exit doesn’t trigger stack unwinding – senx Aug 16 '19 at 20:18
4

The code responsible for printing that error output is called as a destructor (fini) procedure. Since your program terminates without calling any of the process destructors (due to the SIGINT), you do not get any error printouts.

Neowizard
  • 2,981
  • 1
  • 21
  • 39
  • Can you point me to some relevant information about this? and why if I install a SIGINT handler in my code calling just `exit()` the "destructor" is not called? Sorry but I'm a bit lost here because using other libraries, like SDL, I can kill his main loop with SIGINT and I get a report. Thank you. – Bungow Aug 22 '18 at 10:31
  • I'm not sure. The ASAN code clearly shows it runs using atexit, but I can't figure out when the registration happens. Perhaps Kamil Cuk is right and it registers the handler *after* `main` completes...which seems odd – Neowizard Aug 22 '18 at 20:14
  • @Neowizard The registration happens in `__lsan_init` function (see [lsan.cpp]( https://github.com/llvm-mirror/compiler-rt/blob/master/lib/lsan/lsan.cpp)) which is registered during preinit stage (i.e. before all C/C++ constructors). – yugr Sep 20 '20 at 19:49
  • I kind of have the same issue, added address sanitizer instrumentation into my code base at work and now our program dies rather quickly, but no sanitizer related output to give me any clue as to why. – JoeManiaci Aug 10 '22 at 22:12
3

__lsan_do_leak_check() can help you avoid using longjmp in @KamilCuk's answer:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sanitizer/lsan_interface.h>

void handler (int signum) {
   __lsan_do_leak_check();
}

int main(int argc, char *argv[])
{
   signal(SIGINT, handler);
   char *a = malloc(1024);
   a=0;
   printf("lost pointer\n");
   for(;;);
   return 1;
}

Demo:

clang test.c -fsanitize=address -fno-omit-frame-pointer -g -O0 -o test && ./test
lost pointer
  C-c C-c
=================================================================
==29365==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1024 byte(s) in 1 object(s) allocated from:
    #0 0x4c9ca3 in malloc (/home/bjacob/test+0x4c9ca3)
    #1 0x4f9187 in main /home/bjacob/test.c:13:14
    #2 0x7fbc9898409a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2409a)

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

Note I added a=0 to create a leak.

I also added a printf. Without that printf, the leak error is not printed. I suspect the compiler optimized-out the use of the variable "a" despite my using the -O0 compiler option.

Jacob Burckhardt
  • 391
  • 1
  • 2
  • 10