The Android NDK supports Address Sanitizer (also known as ASan) beginning with API level 27 (Android O MR 1).
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's CPU overhead is roughly 2x, code size overhead is between 50% and 2x, and the memory overhead is large (dependent on your allocation patterns, but on the order of 2x).
Sample App
A sample app shows how to configure a build variant for asan.
Build
To build your app's native (JNI) code with Address Sanitizer, do the following:
ndk-build
In your Application.mk:
APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address
For each module in your Android.mk:
LOCAL_ARM_MODE := arm
CMake
In your module's build.gradle:
android {
defaultConfig {
externalNativeBuild {
cmake {
// Can also use system or none as ANDROID_STL.
arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
}
}
}
}
For each target in your CMakeLists.txt:
target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)
Run
Beginning with Android O MR1 (API level 27) an application can provide a wrap shell script that can wrap or replace the application process. This allows a debuggable application to customize their application startup, which enables using ASan on production devices.
- Add
android:debuggable
to the application manifest. - Set
useLegacyPackaging
totrue
in your app'sbuild.gradle
file. See the wrap shell script guide for more information. - Add the ASan runtime library to your app module's
jniLibs
. Add
wrap.sh
files with the following contents to each directory in yoursrc/main/resources/lib
directory.#!/system/bin/sh HERE="$(cd "$(dirname "$0")" && pwd)" export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 ASAN_LIB=$(ls $HERE/libclang_rt.asan-*-android.so) if [ -f "$HERE/libc++_shared.so" ]; then # Workaround for https://github.com/android-ndk/ndk/issues/988. export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" else export LD_PRELOAD="$ASAN_LIB" fi "$@"
Assuming your project's application module is named app
, your final directory
structure should include the following:
<project root>
└── app
└── src
└── main
├── jniLibs
│ ├── arm64-v8a
│ │ └── libclang_rt.asan-aarch64-android.so
│ ├── armeabi-v7a
│ │ └── libclang_rt.asan-arm-android.so
│ ├── x86
│ │ └── libclang_rt.asan-i686-android.so
│ └── x86_64
│ └── libclang_rt.asan-x86_64-android.so
└── resources
└── lib
├── arm64-v8a
│ └── wrap.sh
├── armeabi-v7a
│ └── wrap.sh
├── x86
│ └── wrap.sh
└── x86_64
└── wrap.sh
Stack traces
Address Sanitizer needs to unwind the stack on every malloc
/realloc
/free
call. There are two options here:
A "fast" frame pointer-based unwinder. This is what is used by following the instructions in the building section.
A "slow" CFI unwinder. In this mode ASan uses
_Unwind_Backtrace
. It requires only-funwind-tables
, which is normally enabled by default.
The fast unwinder is the default for malloc/realloc/free. The slow unwinder is
the default for fatal stack traces. The slow unwinder can be enabled for all
stack traces by adding fast_unwind_on_malloc=0
to the ASAN_OPTIONS
variable
in your wrap.sh.