ASan

Learning Outcome

Compile with the address sanitizer flag and interpret the messages generated by ASan.

Introduction

ASan (or Address Sanitizer) is a tool developed by Google to help debug and detect a variety of memory errors including use after free and accessing stack, heap, and global buffer overflows. It provides a stack trace of the invalid memory access and often a map of the memory.

Applicable subjects

COMP1521, COMP2521


Compiling For Use With ASan

In order to enable the address sanitizer, the program must be compiled with the appropriate flags:

$ gcc -g -fsanitize=address -static-libasan -o <program> <program.c>

Using ASan

Address sanitizer will then be activated when the program is run as normal:

$ ./<program>

If a buffer overflow (in the stack, heap or global memory areas) or use after free occurs, ASAN is activated. ASAN then generates a stack trace and memory map of the invalid memory access.

Interpreting ASan

There are four important parts of an ASan output:

  1. The type of invalid memory access (i.e. “heap-use-after-free”, “heap-buffer-overflow”, “stack-buffer-overflow”, “global-buffer-overflow”).

  2. The size and address of the invalid memory access and whether it was a read or write.

  3. The stack trace from the point in the program that the invalid memory was accessed.

  4. A map of the addressable and non-addressable memory addresses. This map displays the address which caused the bug by surrounding it with brackets.

An example of the analysis of this output is given in the example section.


Example

Sometimes, when we run code with buffer overflow, the program will run without any bugs. However, we cannot guarantee this so we must ensure all of the array indicies we access are valid. Luckily ASan can help. In this example, we will use ASan to check if we are accessing invalid memory addresses.

illegal_list.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

int main(void){
    
    int array[10];
    int i = 0;
    while (i <= 10) {
        array[i] = i;
        i++;
    }

    i = 0;
    while (i <= 10) {
        printf("%d ", array[i]);
        i++;
    }

    printf("\n");

    return 0;
}

If we compile this program with the sanitizer flags, then run it, we get the following output:

$ gcc -g -fsanitize=address -static-libasan -o illegal_array illegal_array.c
$ ./illegal_array
=================================================================
==26868==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffec98480b8 at pc 0x560887e014c5 bp 0x7ffec9848050 sp 0x7ffec9848048
WRITE of size 4 at 0x7ffec98480b8 thread T0
    #0 0x560887e014c4 in main /tmp_amd/glass/export/glass/2/z5161594/1511tute/illegal_array.c:9
    #1 0x7fc8f71ac09a in __libc_start_main ../csu/libc-start.c:308
    #2 0x560887cfe429 in _start (/tmp_amd/glass/export/glass/2/z5161594/1511tute/illegal_array+0x8429)

Address 0x7ffec98480b8 is located in stack of thread T0 at offset 72 in frame
    #0 0x560887e013f8 in main /tmp_amd/glass/export/glass/2/z5161594/1511tute/illegal_array.c:4

This frame has 1 object(s):
    [32, 72) 'array' <== Memory access at offset 72 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
    (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /tmp_amd/glass/export/glass/2/z5161594/1511tute/illegal_array.c:9 in main
Shadow bytes around the buggy address:
0x100059300fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059300fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059300fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059300ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059301000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1
=>0x100059301010: f1 f1 00 00 00 00 00[f2]f2 f2 f3 f3 f3 f3 00 00
0x100059301020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059301030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059301040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059301050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059301060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
Freed heap region:       fd
Stack left redzone:      f1
Stack mid redzone:       f2
Stack right redzone:     f3
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

This output contains several points of interest:


==26868==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffec98480b8 at pc 0x560887e014c5 bp 0x7ffec9848050 sp 0x7ffec9848048

This line tells us that the issue was a stack-buffer-overflow. An example of a stack-buffer overflow is accessing an illegal (local) array index.


WRITE of size 4 at 0x7ffec98480b8 thread T0

This line tells us that the program tried to write 4 bytes (could be 4 characters, an integer on certain machines, or a pointer on 32 bit machines).


#0 0x560887e014c4 in main /tmp_amd/glass/export/glass/2/z5161594/1511tute/illegal_array.c:9
#1 0x7fc8f71ac09a in __libc_start_main ../csu/libc-start.c:308
#2 0x560887cfe429 in _start (/tmp_amd/glass/export/glass/2/z5161594/1511tute/illegal_array+0x8429)

This line tells us that the overflow occurred on line 9 of illegal_array.c. This line is:

array[i] = i;

The program is indeed trying to write an integer (4 bytes on this computer) to the stack.


Shadow bytes around the buggy address:
0x100059300fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059300fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059300fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059300ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059301000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1
=>0x100059301010: f1 f1 00 00 00 00 00[f2]f2 f2 f3 f3 f3 f3 00 00
0x100059301020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059301030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059301040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059301050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100059301060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable:           00

This is a map of the memory around the address which caused the stack-buffer-overflow. The legend says that one of the shadow bytes (i.e. one of the ‘00’s or ‘f1’s etc.) represents 8 bytes (i.e. two integers).


=>0x100059301010: f1 f1 00 00 00 00 00[f2]f2 f2 f3 f3 f3 f3 00 00

This is the important line. The 00 00 00 00 00 represents 40 accessible bytes (i.e. our array of size 10).

The invalid memory access is surrounded by square brackets. This is the [f2]. It is the memory address to the right of the array. Therfore, we can deduce that this is accessing too far off the end of the array.

We take a second loop at the loop and realise that there is an off by one error (i.e. i <= 10 should be i < 10).

We fix the two while loops and compile and run with ASan, and there are no more errors:

0 1 2 3 4 5 6 7 8 9

Module author: Liz Willer <e.willer@unsw.edu.au>

Date

2020-01-17