📜 ⬆️ ⬇️

Address Sanitizer, or What if valgrind doesn't work

There was a difficult situation. There is a code written in C, which is actively used through cgo in a project written in Go. At some point, the program began to fall with errors from malloc: either segfault, then memory corruption.

A logical thought: you need valgrind with its memcheck to check who climbs across the batka into the inferno into non-valid memory. However, trying to feed Valgrinda the binary received from go build will only lead to disappointment - even on a simple Hello World, Valgrind will break out with hundreds of errors and send the developer to known coordinates (spoiler: " Go fix your program! ").

This is due to the fact that the go runtime is quite specific and differs significantly from that in C. (Details can be easily found on the request “golang valgrind”).
')
So how do we figure out what's going on?

Close Google has shown me that gcc (and clang, by the way) has a very handy tool - Address Sanitizer . It is convenient in that it can do things that are subject to valgrind (at least it can catch the use of freed memory, overflow and leakage), and it is automatically built into the binary without the need to use external utilities. But the main thing is that it can be safely used in CGo for debugging the C-code without interference for the Go runtime (in fact, the Go developers themselves recommend using this tool).

How to use it?

  1. Check that gcc is at least version 4.8.
  2. Compile our program with the -fsanitize = address flag (many more sanitizers are described in the gcc documentation, you can see it here ). This flag must be added both during assembly and linking (in both CFLAGS and LDFLAGS).
  3. Run the program and enjoy the colorful output of the sanitizer.

For verification, I wrote a simple program that allocates space for 10 ints, but fills 11 (test.c):

#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int *m = (int *) malloc(10 * sizeof (int)); for (int i = 0; i < 11; i++) { m[i] = i; } return 0; } 

Compile it with -fsanitize = address, after launch we see this:

 webconn@webconn-laptop:~/Projects/Testing/C/Sanitizer$ gcc -o ./main ./test.c -fsanitize=address webconn@webconn-laptop:~/Projects/Testing/C/Sanitizer$ ./main ================================================================= ==18098==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60400000dff8 at pc 0x557945c74922 bp 0x7fff94c67d40 sp 0x7fff94c67d38 WRITE of size 4 at 0x60400000dff8 thread T0 #0 0x557945c74921 in main (/home/webconn/Projects/Testing/C/Sanitizer/main+0x921) #1 0x7fea6440b2b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0) #2 0x557945c747a9 in _start (/home/webconn/Projects/Testing/C/Sanitizer/main+0x7a9) 0x60400000dff8 is located 0 bytes to the right of 40-byte region [0x60400000dfd0,0x60400000dff8) allocated by thread T0 here: #0 0x7fea6484ad28 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.3+0xc1d28) #1 0x557945c748c8 in main (/home/webconn/Projects/Testing/C/Sanitizer/main+0x8c8) #2 0x7fea6440b2b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0) SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/webconn/Projects/Testing/C/Sanitizer/main+0x921) in main Shadow bytes around the buggy address: 0x0c087fff9ba0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9bb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9bc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9bd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9be0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x0c087fff9bf0: fa fa fa fa fa fa fa fa fa fa 00 00 00 00 00[fa] 0x0c087fff9c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9c40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Heap right redzone: fb Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack partial redzone: f4 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==18098==ABORTING 

In fact, it is still quite conveniently highlighted in colors:

Color output (PNG, 78.5 KB)
image

In order for this to work with cgo, I added the appropriate special comments when connecting the code to C (your useful flags and directives could be in place of dots):

 /* #cgo linux LDFLAGS: ... -fsanitize=address #cgo linux CFLAGS: ... -fsanitize=address ... */ import "C" 

That gave the desired result - now the module written in C, gives debugging information for memory problems, but does not interfere with the Go runtime.

Actually, the Go developers themselves recommend using Sanitizer in such situations.

I hope the information will be useful to someone.

UPD: Of course, if you add the -g flag, the output will be slightly more readable.
Output after compilation with the -g flag
 webconn@webconn-laptop:~/Projects/Testing/C/Sanitizer$ ./main ================================================================= ==12266==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60400000dff8 at pc 0x55d4f8d03922 bp 0x7ffc10a57370 sp 0x7ffc10a57368 WRITE of size 4 at 0x60400000dff8 thread T0 #0 0x55d4f8d03921 in main /home/webconn/Projects/Testing/C/Sanitizer/main.c:14 #1 0x7fa688d282b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0) #2 0x55d4f8d037a9 in _start (/home/webconn/Projects/Testing/C/Sanitizer/main+0x7a9) 0x60400000dff8 is located 0 bytes to the right of 40-byte region [0x60400000dfd0,0x60400000dff8) allocated by thread T0 here: #0 0x7fa689167d28 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.3+0xc1d28) #1 0x55d4f8d038c8 in main /home/webconn/Projects/Testing/C/Sanitizer/main.c:11 #2 0x7fa688d282b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0) SUMMARY: AddressSanitizer: heap-buffer-overflow /home/webconn/Projects/Testing/C/Sanitizer/main.c:14 in main Shadow bytes around the buggy address: 0x0c087fff9ba0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9bb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9bc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9bd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9be0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x0c087fff9bf0: fa fa fa fa fa fa fa fa fa fa 00 00 00 00 00[fa] 0x0c087fff9c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9c40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Heap right redzone: fb Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack partial redzone: f4 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==12266==ABORTING 

Source: https://habr.com/ru/post/323380/


All Articles