AddressSanitizer

AddressSanitizer(ASan)는 네이티브 코드의 메모리 버그 감지를 위한 빠른 컴파일러 기반 도구입니다.

ASan은 다음을 감지합니다.

  • 스택 및 힙 버퍼 오버플로우/언더플로우
  • 프리 후 힙 사용
  • 범위를 벗어난 스택 사용
  • 더블 프리/와일드 프리

ASan은 32비트 및 64비트ARM과 x86 및 x86-64에서 실행됩니다. ASan의 CPU 오버헤드는 약 2x, 코드 크기 오버헤드는 50%와 2x 사이이며, 대량 메모리 오버헤드는 할당 패턴에 따라 다르지만 대략 2x 정도입니다.

AArch64의 Android 10 및 AOSP 마스터 분기는 하드웨어 가속 ASan(HWASan)을 지원합니다. 이는 RAM 오버헤드는 낮지만 감지된 버그의 범위는 더 큰 유사 도구입니다. HWASan은 ASan에 의해 감지된 버그 이외에 반환 이후의 스택 사용도 감지합니다.

HWASan은 CPU 및 코드 크기 오버헤드가 비슷하지만 RAM 오버헤드는 15%로 훨씬 작습니다. HWASan은 비확정적입니다. 가능한 태그 값은 256개가 전부이므로 버그를 놓칠 확률은 0.4%로 고정됩니다. HWASan에는 ASan처럼 오버플로우 감지를 위한 제한된 크기의 레드존과 use-after-free 감지를 위한 제한된 용량 격리 저장소가 없습니다. 따라서 오버플로우 크기나 메모리가 할당 취소된 시점은 HWASan과 상관이 없습니다. 이는 HWASan이 ASan보다 나은 이유입니다. HWASan 설계 또는 Android의 HWASan 사용 방법에 관해 자세히 알아보세요.

ASan은 힙 오버플로우 외에 스택/전역 오버플로우도 감지하며, 최소 메모리 오버헤드 덕분에 빠릅니다.

이 문서에서는 ASan으로 Android의 일부/전체를 빌드하고 실행하는 방법에 관해 설명합니다. ASan으로 SDK/NDK 앱을 빌드하는 경우에는 Address Sanitizer를 참조하세요.

ASan으로 개별 실행 파일 완전 삭제

실행 파일의 빌드 규칙에 LOCAL_SANITIZE:=address 또는 sanitize: { address: true }를 추가합니다. 코드에서 기존 예를 검색하거나 다른 가용한 새니타이저를 찾을 수 있습니다.

버그가 감지되면 ASan은 상세 보고서를 표준 출력 및 logcat에 출력한 다음 프로세스를 비정상 종료합니다.

ASan으로 공유 라이브러리 완전 삭제

ASan으로 빌드된 라이브러리는 ASan으로 빌드된 실행 파일만 사용 가능하며, 이는 ASan의 작동 방식 때문입니다.

일부만 ASan으로 빌드된 여러 실행 파일에 사용된 공유 라이브러리를 완전히 삭제하려면 라이브러리의 사본 2개가 필요합니다. 권장되는 방법은 다음을 해당 모듈의 Android.mk에 추가하는 것입니다.

LOCAL_SANITIZE:=address
LOCAL_MODULE_RELATIVE_PATH := asan

이렇게 하면 라이브러리가 /system/lib 대신 /system/lib/asan에 놓입니다. 이어서 다음을 사용하여 실행 파일을 실행합니다.

LD_LIBRARY_PATH=/system/lib/asan

시스템 데몬의 경우 다음을 /init.rc 또는 /init.$device$.rc의 적절한 섹션에 추가합니다.

setenv LD_LIBRARY_PATH /system/lib/asan

프로세스가 존재하는 경우 /proc/$PID/maps를 읽어 프로세스가 /system/lib/asan의 라이브러리를 사용 중인지 확인하세요. 사용하지 않는 경우 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.

향상된 스택 트레이스

ASan은 빠른 프레임 포인터 기반 언와인더를 사용하여 프로그램의 모든 메모리 할당 및 할당 해제 이벤트의 스택 트레이스를 기록합니다. 대부분의 Android는 프레임 포인터 없이 빌드됩니다. 따라서 한두 개의 의미 있는 프레임만 수신되는 경우가 많습니다. 이를 해결하려면 ASan(권장됨) 또는 다음으로 라이브러리를 재구성하세요.

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

아니면 프로세스 환경에서 ASAN_OPTIONS=fast_unwind_on_malloc=0을 설정합니다. 두 번째 방법은 로드에 따라 CPU를 많이 사용할 수도 있습니다.

기호화

처음에는 ASan 보고서에 바이너리 및 공유 라이브러리의 오프셋에 대한 참조가 포함되었습니다. 소스 파일과 행 정보를 가져오는 방법에는 두 가지가 있습니다.

  • llvm-symbolizer 바이너리가 /system/bin에 있는지 확인합니다. llvm-symbolizerthird_party/llvm/tools/llvm-symbolizer의 소스에서 빌드됩니다.
  • external/compiler-rt/lib/asan/scripts/symbolize.py 스크립트를 통해 보고서를 필터링합니다.

두 번째 방식을 사용하면 호스트에서 기호화된 라이브러리의 가용성 때문에 더 많은 데이터(file:line 위치)를 제공할 수 있습니다.

앱의 ASan

ASan은 자바 코드의 내용을 볼 수 없지만 JNI 라이브러리의 버그는 감지할 수 있습니다. 이를 위해서는 ASan으로 실행 파일을 빌드해야 하며 이 경우에는 /system/bin/app_process(32|64)입니다. 이렇게 하면 ASan이 기기의 모든 앱에 동시에 사용 설정됩니다. 이는 상당히 큰 로드이지만 2 GB RAM을 보유한 기기 정도면 충분히 처리할 수 있습니다.

LOCAL_SANITIZE:=addressframeworks/base/cmds/app_processapp_process 빌드 규칙에 추가합니다. 일단은 같은 파일의 app_process__asan 타겟을 무시합니다(읽는 시점에도 계속해서 있는 경우).

관련 system/core/rootdir/init.zygote(32|64).rc 파일에 있는 service zygote 섹션을 수정하여 class main이 포함되어 있으며 들여쓰기된 행의 블록에 다음 행을 추가하고 동일하게 들여쓰기하세요.

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

빌드하고, adb sync를 실행하고, fastboot 플래시 부팅을 한 후 재부팅합니다.

래핑 속성 사용

이전 섹션의 접근 방식을 사용하면 ASan이 시스템의 모든 앱에 삽입됩니다(실질적으로는 Zygote 프로세스의 모든 하위 프로세스에 삽입됨). ASan으로 1개 또는 여러 앱을 실행하여 일부 메모리 오버헤드를 느려진 앱 시작과 맞교환하는 것이 가능합니다.

이를 위해서는 앱을 wrap. 속성으로 시작해야 합니다. 다음 예시는 ASan 밑에서 Gmail 앱을 실행합니다.

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

이 컨텍스트에서는 asanwrapper/system/bin/app_process를 ASan으로 빌드된 /system/bin/asan/app_process에 다시 씁니다. 또한 동적 라이브러리 검색 경로 시작 부분에 /system/lib/asan을 추가합니다. 이렇게 하면 asanwrapper로 실행할 경우 ASan으로 계측화된 /system/lib/asan의 라이브러리가 /system/lib의 일반 라이브러리보다 선호됩니다.

버그가 감지되면 앱이 다운되고 로그에 보고서가 출력됩니다.

SANITIZE_TARGET

Android 7.0 이상에는 ASan으로 한 번에 전체 Android 플랫폼을 빌드하기 위한 지원 기능이 포함됩니다. Android 9보다 높은 출시를 빌드하는 경우에는 HWASan을 선택하는 것이 좋습니다.

같은 빌드 트리에서 다음 명령어를 실행합니다.

make -j42
SANITIZE_TARGET=address make -j42

이 모드에서는 userdata.img가 추가 라이브러리를 포함하며, 기기에도 플래시되어야 합니다. 다음 명령줄을 사용합니다.

fastboot flash userdata && fastboot flashall

이렇게 하면 /system/lib의 일반 공유 라이브러리 집합(첫 번째 make 호출)과 ASan으로 계측화된 /data/asan/lib의 공유 라이브러리 집합(두 번째 make 호출)이 빌드됩니다. 두 번째 빌드의 실행 파일이 첫 번째 빌드의 실행 파일을 덮어씁니다. ASan으로 계측화된 실행 파일은 /system/lib 전에 /data/asan/lib을 포함하는 다른 라이브러리 검색 경로를 가져오며, 이때 PT_INTERP/system/bin/linker_asan을 사용합니다.

$SANITIZE_TARGET 값이 변경되면 빌드 시스템은 중간 객체 디렉터리를 클로버링합니다. 이렇게 하면 모든 타겟의 재구성이 강제되는 동시에 /system/lib 아래에 설치된 바이너리는 보전됩니다.

일부 타겟은 ASan으로 빌드할 수 없습니다.

  • 정적으로 연결된 실행 파일
  • LOCAL_CLANG:=false 타겟
  • LOCAL_SANITIZE:=falseSANITIZE_TARGET=address에 관해 ASan이 적용되지 않음

이러한 실행 파일은 SANITIZE_TARGET 빌드에서 생략되며 첫 번째 make 호출의 버전이 /system/bin에 남습니다.

이러한 라이브러리는 ASan 없이 빌드되며, 종속된 정적 라이브러리에서 가져온 일부 ASan 코드를 포함할 수 있습니다.

지원 문서