LLVM Sanitizers

LLVM, the compiler infrastructure used to build Android, contains multiple components that perform static and dynamic analysis. Of these components, the sanitizers—specifically AddressSanitizer and UndefinedBehaviorSanitizer—can be used extensively to analyze Android. Sanitizers are compiler-based instrumentation components contained in external/compiler-rt that can be used during development and testing to push out bugs and make Android better. Android's current set of sanitizers can discover and diagnose many memory misuse bugs and potentially dangerous undefined behavior.

It's best practice for Android builds to boot and run with sanitizers enabled, such as AddressSanitizer and UndefinedBehaviorSanitizer. This page introduces AddressSanitizer, UndefinedBehaviorSanitizer, and KernelAddressSanitizer, shows how they can be used within the Android build system, and gives example Android.mk and Android.bp files that build native components with these sanitizers enabled.

AddressSanitizer

AddressSanitizer (ASan) is a compiler-based instrumentation capability that detects many types of memory errors in C/C++ code at runtime. ASan can detect many classes of memory errors, including:

  • Out-of-bounds memory access
  • Double free
  • Use-after-free

Android allows for ASan instrumentation at the full-build level and the app level with asanwrapper.

AddressSanitizer combines instrumentation of all memory-related function calls—including alloca, malloc, and free—and padding all variables and allocated memory regions with memory that triggers an ASan callback when it is read or written to.

The instrumentation lets ASan detect invalid memory usage bugs, including double-free, and use-after scope, return, and free, while the memory-region padding detects out-of-bounds read or writes. If a read or write to this padding region occurs, ASan catches it and outputs information to help diagnosing the memory violation, including the call stack, shadow memory map, the type of memory violation, what was read or written, the instruction that caused the violation, and the memory contents.

pixel-xl:/ # sanitizer-status                                                                                            
=================================================================
==14164==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x0032000054b0 at pc 0x005df16ffc3c bp 0x007fc236fdf0 sp 0x007fc236fdd0
WRITE of size 1 at 0x0032000054b0 thread T0
    #0 0x5df16ffc3b in test_crash_malloc sanitizer-status/sanitizer-status.c:36:13
    #1 0x5df17004e3 in main sanitizer-status/sanitizer-status.c:76:7
    #2 0x794cf665f3 in __libc_init (/system/lib64/libc.so+0x1b5f3)
    #3 0x5df16ffa53 in do_arm64_start (/system/bin/sanitizer-status+0xa53)

0x0032000054b0 is located 0 bytes to the right of 32-byte region [0x003200005490,0x0032000054b0)
allocated by thread T0 here:
    #0 0x794d0bdc67 in malloc (/system/lib64/libclang_rt.asan-aarch64-android.so+0x74c67)
    #1 0x5df16ffb47 in test_crash_malloc sanitizer-status/sanitizer-status.c:34:25
    #2 0x5df17004e3 in main sanitizer-status/sanitizer-status.c:76:7
    #3 0x794cf665f3 in __libc_init (/system/lib64/libc.so+0x1b5f3)
    #4 0x5df16ffa53 in do_arm64_start (/system/bin/sanitizer-status+0xa53)
    #5 0x794df78893  (<unknown module>)

SUMMARY: AddressSanitizer: heap-buffer-overflow sanitizer-status/sanitizer-status.c:36:13 in test_crash_malloc

Sometimes, the bug discovery process can appear to be non-deterministic, especially for bugs that require special setup or more advanced techniques, such as heap priming or race condition exploitation. Many of these bugs are not immediately apparent, and could surface thousands of instructions away from the memory violation that was the actual root cause. ASan instruments all memory-related functions and pads data with areas that cannot be accessed without triggering an ASan callback. This means that memory violations are caught the instant they occur, instead of waiting for a crash-inducing corruption. This is extremely useful in bug discovery and root cause diagnosis.

To verify that ASAN is functional on a target device, Android has included the asan_test executable. The asan_test executable tests and validates the ASAN functionality on a target device, giving diagnostics messages with the status of each test. When using an ASAN Android build, it is located in /data/nativetest/asan_test/asan_test or /data/nativetest64/asan_test/asan_test by default.

UndefinedBehaviorSanitizer

UndefinedBehaviorSanitizer (UBSan) performs compile-time instrumentation to check for various types of undefined behavior. While UBSan is capable of detecting many undefined behaviors, Android supports alignment, bool, bounds, enum, float-cast-overflow, float-divide-by-zero, integer-divide-by-zero, nonnull-attribute, null, return, returns-nonnull-attribute, shift-base, shift-exponent, signed-integer-overflow, unreachable, unsigned-integer-overflow, and vla-bound. unsigned-integer-overflow, while not technically an undefined behavior, is included in the sanitizer and used in many Android modules, including the mediaserver components, to eliminate any latent integer-overflow vulnerabilities.

Implementation

In the Android build system, you can enable UBSan globally or locally. To enable UBSan globally, set SANITIZE_TARGET in Android.mk. To enable UBSan at a per-module level, set LOCAL_SANITIZE and specify the undefined behaviors that you want to look for in Android.mk. For example:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_CFLAGS := -std=c11 -Wall -Werror -O0

LOCAL_SRC_FILES:= sanitizer-status.c

LOCAL_MODULE:= sanitizer-status
LOCAL_MODULE_TAGS := debug

LOCAL_SANITIZE := alignment bounds null unreachable integer
LOCAL_SANITIZE_DIAG := alignment bounds null unreachable integer

include $(BUILD_EXECUTABLE)

The Android build system does not yet support as detailed diagnostics in blueprint files as it does makefiles. Here is the closest equivalent written as a blueprint (Android.bp):

cc_binary {

    cflags: [
        "-std=c11",
        "-Wall",
        "-Werror",
        "-O0",
    ],

    srcs: ["sanitizer-status.c"],

    name: "sanitizer-status",
    tags: ["debug"],

    sanitize: {
        misc_undefined: [
            "alignment",
            "bounds",
            "null",
            "unreachable",
            "integer",
        ],
        diag: {
            undefined : true
        },
    },

}

UBSan shortcuts

Android also has two shortcuts, integer and default-ub, to enable a set of sanitizers at the same time. integer enables integer-divide-by-zero, signed-integer-overflow and unsigned-integer-overflow. default-ub enables the checks that have minimal compiler performance issues: bool, integer-divide-by-zero, return, returns-nonnull-attribute, shift-exponent, unreachable and vla-bound. The integer sanitizer class can be used with SANITIZE_TARGET and LOCAL_SANITIZE, while default-ub can only be used with SANITIZE_TARGET.

Better error reporting

Android's default UBSan implementation invokes a specified function when undefined behavior is encountered. By default, this function is abort. However, starting in October 2016, UBSan on Android has an optional runtime library that gives more detailed error reporting, including type of undefined behavior encountered, file and source code line information. To enable this error reporting with integer checks add the following to an Android.mk file:

LOCAL_SANITIZE:=integer
LOCAL_SANITIZE_DIAG:=integer

The LOCAL_SANITIZE value enables the sanitizer during the build. LOCAL_SANITIZE_DIAG turns on diagnostic mode for the specified sanitizer. It is possible to set LOCAL_SANITIZE and LOCAL_SANITIZE_DIAG to different values, but only those checks in LOCAL_SANITIZE are enabled. If a check is not specified in LOCAL_SANITIZE, but is specified in LOCAL_SANITIZE_DIAG, the check isn't enabled and diagnostic messages aren't given.

Here is an example of the information provided by the UBSan runtime library:

pixel-xl:/ # sanitizer-status ubsan
sanitizer-status/sanitizer-status.c:53:6: runtime error: unsigned integer overflow: 18446744073709551615 + 1 cannot be represented in type 'size_t' (aka 'unsigned long')

Kernel Address Sanitizer

Similar to the LLVM-based sanitizers for userspace components, Android includes the Kernel Address Sanitizer (KASAN). KASAN is a combination of kernel and compile time modifications that result in an instrumented system that allows for simpler bug discovery and root cause analysis.

KASAN can detect many types of memory violations in the kernel. It can also detect out-of-bound reads and writes on stack, heap and global variables, and can detect use-after-free and double frees.

Similar to ASAN, KASAN uses a combination of memory-function instrumentation at compile time and shadow memory to track memory accesses at runtime. In KASAN, an eighth of the kernel memory space is dedicated to shadow memory, which determines if a memory access is valid or not.

KASAN is supported on x86_64 and arm64 architectures. It has been part of the upstream kernel since 4.0, and has been backported to Android 3.18-based kernels. KASAN has been tested on Android kernels compiled with gcc based on 4.9.2.

In addition to KASAN, kcov is another kernel modification that is useful for testing. kcov was developed to allow for coverage-guided fuzz testing in the kernel. It measures coverage in terms of syscall inputs and is useful with fuzzing systems, such as syzkaller.

Implementation

To compile a kernel with KASAN and kcov enabled, add the following build flags to your kernel build configuration:

CONFIG_KASAN 
CONFIG_KASAN_INLINE 
CONFIG_TEST_KASAN 
CONFIG_KCOV 
CONFIG_SLUB 
CONFIG_SLUB_DEBUG 
CONFIG_CC_OPTIMIZE_FOR_SIZE

And removing the following:

CONFIG_SLUB_DEBUG_ON 
CONFIG_SLUB_DEBUG_PANIC_ON 
CONFIG_KASAN_OUTLINE 
CONFIG_KERNEL_LZ4

Then build and flash your kernel as usual. The KASAN kernel is considerably larger than the original. If applicable, modify any boot parameters and bootloader settings to take this into consideration.

After flashing the kernel, check the kernel boot logs to see if KASAN is enabled and running. The kernel will start up with memory map information for KASAN, such as:

...
[    0.000000] c0      0 Virtual kernel memory layout:
[    0.000000] c0      0     kasan   : 0xffffff8000000000 - 0xffffff9000000000   (    64 GB)
[    0.000000] c0      0     vmalloc : 0xffffff9000010000 - 0xffffffbdbfff0000   (   182 GB)
[    0.000000] c0      0     vmemmap : 0xffffffbdc0000000 - 0xffffffbfc0000000   (     8 GB maximum)
[    0.000000] c0      0               0xffffffbdc0000000 - 0xffffffbdc3f95400   (    63 MB actual)
[    0.000000] c0      0     PCI I/O : 0xffffffbffa000000 - 0xffffffbffb000000   (    16 MB)
[    0.000000] c0      0     fixed   : 0xffffffbffbdfd000 - 0xffffffbffbdff000   (     8 KB)
[    0.000000] c0      0     modules : 0xffffffbffc000000 - 0xffffffc000000000   (    64 MB)
[    0.000000] c0      0     memory  : 0xffffffc000000000 - 0xffffffc0fe550000   (  4069 MB)
[    0.000000] c0      0       .init : 0xffffffc001d33000 - 0xffffffc001dce000   (   620 KB)
[    0.000000] c0      0       .text : 0xffffffc000080000 - 0xffffffc001d32284   ( 29385 KB)
...

And this is how a bug will look:

[   18.539668] c3      1 ==================================================================
[   18.547662] c3      1 BUG: KASAN: null-ptr-deref on address 0000000000000008
[   18.554689] c3      1 Read of size 8 by task swapper/0/1
[   18.559988] c3      1 CPU: 3 PID: 1 Comm: swapper/0 Tainted: G        W      3.18.24-xxx #1
[   18.569275] c3      1 Hardware name: Android Device
[   18.577433] c3      1 Call trace:
[   18.580739] c3      1 [<ffffffc00008b32c>] dump_backtrace+0x0/0x2c4
[   18.586985] c3      1 [<ffffffc00008b600>] show_stack+0x10/0x1c
[   18.592889] c3      1 [<ffffffc001481194>] dump_stack+0x74/0xc8
[   18.598792] c3      1 [<ffffffc000202ee0>] kasan_report+0x11c/0x4d0
[   18.605038] c3      1 [<ffffffc00020286c>] __asan_load8+0x20/0x80
[   18.611115] c3      1 [<ffffffc000bdefe8>] android_verity_ctr+0x8cc/0x1024
[   18.617976] c3      1 [<ffffffc000bcaa2c>] dm_table_add_target+0x3dc/0x50c
[   18.624832] c3      1 [<ffffffc001bdbe60>] dm_run_setup+0x50c/0x678
[   18.631082] c3      1 [<ffffffc001bda8c0>] prepare_namespace+0x44/0x1ac
[   18.637676] c3      1 [<ffffffc001bda170>] kernel_init_freeable+0x328/0x364
[   18.644625] c3      1 [<ffffffc001478e20>] kernel_init+0x10/0xd8
[   18.650613] c3      1 ==================================================================

In addition, if modules are enabled in your kernel, you can load the test_kasan kernel module for further testing. The module attempts out-of-bounds memory accesses and use-after-free and is useful in testing KASAN on a target device.