AddressSanitizer

AddressSanitizer (ASan) is a fast compiler-based tool for detecting memory bugs in native code.

ASan detects:

  • Stack and heap buffer overflow/underflow
  • Heap use after free
  • Stack use outside scope
  • Double free/wild free

ASan runs on both 32-bit and 64-bit ARM, plus x86 and x86-64. ASan's CPU overhead is roughly 2x, code size overhead is between 50% and 2x, and a large memory overhead (dependent on your allocation patterns, but on the order of 2x).

Android 10 and the AOSP main branch on AArch64 support hardware-accelerated ASan (HWASan), a similar tool with lower RAM overhead and a larger range of detected bugs. HWASan detects stack use after return, in addition to the bugs detected by ASan.

HWASan has similar CPU and code size overhead, but a much smaller RAM overhead (15%). HWASan is nondeterministic. There are only 256 possible tag values, so there's a flat 0.4% probability of missing any bug. HWASan doesn't have ASan's limited-size red zones for detecting overflows and limited-capacity quarantine for detecting use-after-free, so it doesn't matter to HWASan how large the overflow is or how long ago the memory was deallocated. This makes HWASan better than ASan. You can read more about the design of HWASan or about the use of HWASan on Android.

ASan detects stack/global overflows in addition to heap overflows, and is fast with minimal memory overhead.

This document describes how to build and run parts/all of Android with ASan. If you're building an SDK/NDK app with ASan, see Address Sanitizer instead.

Sanitizing individual executables with ASan

Add LOCAL_SANITIZE:=address or sanitize: { address: true } to the build rule for the executable. You can search the code for existing examples or to find the other available sanitizers.

When a bug is detected, ASan prints a verbose report both to the standard output and to logcat and then crashes the process.

Sanitizing shared libraries with ASan

Due to the way ASan works, a library built with ASan can only be used by an executable that's built with ASan.

To sanitize a shared library that's used in multiple executables, not all of which are built with ASan, you need two copies of the library. The recommended way to do this is to add the following to Android.mk for the module in question:

LOCAL_SANITIZE:=address
LOCAL_MODULE_RELATIVE_PATH := asan

This puts the library in /system/lib/asan instead of /system/lib. Then, run your executable with:

LD_LIBRARY_PATH=/system/lib/asan

For system daemons, add the following to the appropriate section of /init.rc or /init.$device$.rc.

setenv LD_LIBRARY_PATH /system/lib/asan

Verify that the process is using libraries from /system/lib/asan when present by reading /proc/$PID/maps. If it's not, you may need to disable SELinux:

adb root
adb shell setenforce 0
# restart the process with adb shell kill $PID
# if it is a system service, or may be adb shell stop; adb shell start.

Better stack traces

ASan uses a fast, frame-pointer-based unwinder to record a stack trace for every memory allocation and deallocation event in the program. Most of Android is built without frame pointers. As a result, you often get only one or two meaningful frames. To fix this, either rebuild the library with ASan (recommended!), or with:

LOCAL_CFLAGS:=-fno-omit-frame-pointer
LOCAL_ARM_MODE:=arm

Or set ASAN_OPTIONS=fast_unwind_on_malloc=0 in the process environment. The latter can be very CPU-intensive, depending on the load.

Symbolization

Initially, ASan reports contain references to offsets in binaries and shared libraries. There are two ways to obtain source file and line information:

  • Ensure that the llvm-symbolizer binary is present in /system/bin. llvm-symbolizer is built from sources in third_party/llvm/tools/llvm-symbolizer.
  • Filter the report through the external/compiler-rt/lib/asan/scripts/symbolize.py script.

The second approach can provide more data (that is, file:line locations) because of the availability of symbolized libraries on the host.

ASan in apps

ASan can't see into Java code, but it can detect bugs in the JNI libraries. For that, you need to build the executable with ASan, which in this case is /system/bin/app_process(32|64). This enables ASan in all apps on the device at the same time, which is a heavy load, but a device with 2 GB RAM should be able to handle this.

Add LOCAL_SANITIZE:=address to the app_process build rule in frameworks/base/cmds/app_process. Ignore the app_process__asan target in the same file for now (if it's still there at the time you read this).

Edit the service zygote section of the appropriate system/core/rootdir/init.zygote(32|64).rc file to add the following lines to the block of indented lines containing class main, also indented by the same amount:

    setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib
    setenv ASAN_OPTIONS allow_user_segv_handler=true

Build, adb sync, fastboot flash boot, and reboot.

Using the wrap property

The approach in the previous section puts ASan into every app in the system (actually, into every descendant of the Zygote process). It's possible to run only one (or several) apps with ASan, trading some memory overhead for slower app startup.

This can be done by starting your app with the wrap. property. The following example runs the Gmail app under ASan:

adb root
adb shell setenforce 0  # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"

In this context, asanwrapper rewrites /system/bin/app_process to /system/bin/asan/app_process, which is built with ASan. It also adds /system/lib/asan at the start of the dynamic library search path. This way ASan-instrumented libraries from /system/lib/asan are preferred to normal libraries in /system/lib when running with asanwrapper.

If a bug is found, the app crashes, and the report is printed to the log.

SANITIZE_TARGET

Android 7.0 and higher includes support for building the entire Android platform with ASan at once. (If you're building a release higher than Android 9, HWASan is a better choice.)

Run the following commands in the same build tree.

make -j42
SANITIZE_TARGET=address make -j42

In this mode, userdata.img contains extra libraries and must be flashed to the device as well. Use the following command line:

fastboot flash userdata && fastboot flashall

This builds two sets of shared libraries: normal in /system/lib (the first make invocation), and ASan-instrumented in /data/asan/lib (the second make invocation). Executables from the second build overwrite those from the first build. ASan-instrumented executables get a different library search path that includes /data/asan/lib before /system/lib through the use of /system/bin/linker_asan in PT_INTERP.

The build system clobbers intermediate object directories when the $SANITIZE_TARGET value has changed. This forces a rebuild of all targets while preserving installed binaries under /system/lib.

Some targets can't be built with ASan:

  • Statically linked executables
  • LOCAL_CLANG:=false targets
  • LOCAL_SANITIZE:=false aren't ASan'd for SANITIZE_TARGET=address

Executables like these are skipped in the SANITIZE_TARGET build, and the version from the first make invocation is left in /system/bin.

Libraries like this are built without ASan. They can contain some ASan code from the static libraries that they depend upon.

Supporting documentation