LLVM 새니타이저

Android를 빌드하는 데 사용되는 컴파일러 인프라인 LLVM에는 정적 및 동적 분석을 처리하는 여러 구성요소가 포함되어 있습니다. 이러한 구성요소 중 특히 AddressSanitizer 및 UndefinedBehaviorSanitizer와 같은 새니타이저는 Android를 분석하는 데 광범위하게 사용할 수 있습니다. 새니타이저는 external/compiler-rt에 포함된 컴파일러 기반의 계측 구성요소로서 개발 및 테스트 과정에서 버그를 없애고 Android를 개선하는 데 사용할 수 있습니다. Android의 현재 새니타이저는 많은 메모리 오용 버그와 잠재적 위험성이 있는 정의되지 않은 동작을 발견하고 진단할 수 있습니다.

AddressSanitizer 및 UndefinedBehaviorSanitizer와 같은 새니타이저를 사용 설정하여 Android 빌드를 부팅하고 실행하는 것이 좋습니다. 이 페이지에서는 AddressSanitizer, UndefinedBehaviorSanitizer 및 KernelAddressSanitizer를 소개하고 Android 빌드 시스템 내에서 어떻게 사용할 수 있는지 보여주며, 이러한 새니타이저를 사용 설정하여 네이티브 구성요소를 빌드하는 Android.mk 및 Android.bp 파일의 예를 제시합니다.

AddressSanitizer

AddressSanitizer(ASan)는 런타임 시 C/C++ 코드에서 여러 유형의 메모리 오류를 감지하는 컴파일러 기반의 계측 기능입니다. ASan은 다음과 같은 여러 클래스의 메모리 오류를 감지할 수 있습니다.

  • 범위를 벗어난 메모리 액세스
  • Double free
  • Use-after-free

Android는 전체 빌드 수준에서의 Asan 계측과 asanwrapper를 사용하는 앱 수준을 허용합니다.

AddressSanitizer는 alloca, malloc 및 free를 포함한 모든 메모리 관련 함수 호출의 계측과 모든 변수 및 할당된 메모리 영역 패딩을 읽히거나 쓰일 때 ASan 콜백을 트리거하는 메모리와 결합합니다.

ASan에서는 계측을 사용하여 double-free, use-after-scope, use-after-return, use-after-free와 같은 잘못된 메모리 사용 버그를 감지하고, 메모리 영역 패딩은 범위를 벗어난 읽기 또는 쓰기를 감지합니다. 이 패딩 영역에서 읽기 또는 쓰기가 발생하면 ASan은 이를 포착하고 호출 스택, 섀도 메모리 맵, 메모리 위반 유형, 읽거나 쓴 내용, 위반을 초래한 명령, 메모리 내용을 비롯한 메모리 위반을 진단하는 데 도움을 주는 정보를 출력합니다.

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

버그 발견 프로세스는 특히 힙 프라이밍 또는 경합 상태 취약점 공격과 같이 특수 설정 또는 고급 기법이 필요한 버그의 경우 비결정적으로 보일 수 있습니다. 이러한 버그의 상당수는 즉시 드러나지 않으며 실제 근본 원인인 메모리 위반이 발생한 후 수천 개의 명령이 진행된 후에야 드러날 수 있습니다. ASan은 모든 메모리 관련 함수를 계측하고 ASan 콜백을 트리거하지 않으면 액세스할 수 없는 영역이 있는 데이터를 패딩합니다. 즉, 비정상 종료를 초래하는 손상이 발생할 때까지 기다리는 것이 아니라 메모리 위반이 발생하는 순간에 메모리 위반을 포착합니다. 이러한 방식은 버그 발견 및 근본 원인 진단에 매우 유용합니다.

타겟 기기에서 ASAN이 작동하는지 확인하기 위해 Android에는 asan_test 실행 파일이 포함되어 있습니다. asan_test 실행 파일은 타겟 기기에서 ASAN 기능을 테스트 및 검증하여 각 테스트의 상태가 포함된 진단 메시지를 제공합니다. ASAN Android 빌드를 사용하는 경우 실행 파일은 기본적으로 /data/nativetest/asan_test/asan_test 또는 /data/nativetest64/asan_test/asan_test에 있습니다.

UndefinedBehaviorSanitizer

UndefinedBehaviorSanitizer(UBSan)는 컴파일 시간을 계측하여 다양한 유형의 정의되지 않은 동작을 검사합니다. UBSan은 여러 정의되지 않은 동작을 감지할 수 있고, Android는 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, vla-bound를 지원합니다. unsigned-integer-overflow의 경우 엄밀하게 말하면 정의되지 않은 동작은 아니지만, 잠재적인 integer-overflow 취약점을 제거하기 위해 새니타이저에 포함되어 미디어 서버 구성요소를 비롯한 많은 Android 모듈에서 사용됩니다.

구현

Android 빌드 시스템에서는 UBSan을 전역 또는 로컬에서 사용 설정할 수 있습니다. UBSan을 전역에서 사용 설정하려면 Android.mk에서 SANITIZE_TARGET을 설정합니다. UBSan을 모듈별 수준에서 사용 설정하려면 LOCAL_SANITIZE를 설정하고 Android.mk에서 찾고자 하는 정의되지 않은 동작을 지정합니다. 예:

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_SANITIZE := alignment bounds null unreachable integer
LOCAL_SANITIZE_DIAG := alignment bounds null unreachable integer

include $(BUILD_EXECUTABLE)

Android 빌드 시스템은 makefile처럼 청사진 파일의 세부 진단을 아직 지원하지 않습니다. 다음은 청사진(Android.bp)과 거의 동일하게 작성된 코드입니다.

cc_binary {

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

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

    name: "sanitizer-status",

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

}

UBSan 단축키

Android에는 일련의 새니타이저를 동시에 사용 설정할 수 있는 두 가지 손쉬운 방법으로 integerdefault-ub를 제공합니다. integer는 integer-divide-by-zero, signed-integer-overflowunsigned-integer-overflow를 지원합니다. default-ub는 검사 시 bool, integer-divide-by-zero, return, returns-nonnull-attribute, shift-exponent, unreachable 및 vla-bound의 컴파일러 성능 문제를 최소화합니다. integer 새니타이저 클래스는 SANITIZE_TARGET 및 LOCAL_SANITIZE에서 사용할 수 있지만 default-ub는 SANITIZE_TARGET에서만 사용할 수 있습니다.

오류 신고 개선

Android의 기본 UBSan 구현은 정의되지 않은 동작이 발생할 때 지정된 함수를 호출합니다. 기본적으로 이 함수는 중단됩니다. 하지만 2016년 10월부터 Android UBSan은 발생한 정의되지 않은 동작의 유형, 파일 및 소스 코드 행 정보를 비롯한 더 세부적인 오류 신고 기능을 제공하는 런타임 라이브러리 옵션을 제공합니다. 정수 검사가 포함된 오류 신고 기능을 사용하려면 다음을 Android.mk 파일에 추가합니다.

LOCAL_SANITIZE:=integer
LOCAL_SANITIZE_DIAG:=integer

LOCAL_SANITIZE 값은 빌드하는 동안 새니타이저를 사용 설정합니다. LOCAL_SANITIZE_DIAG는 지정된 새니타이저의 진단 모드를 사용 설정합니다. LOCAL_SANITIZE 및 LOCAL_SANITIZE_DIAG를 서로 다른 값으로 설정할 수 있지만, LOCAL_SANITIZE의 검사만 사용 설정됩니다. 검사가 LOCAL_SANITIZE에 지정되지 않았지만 LOCAL_SANITIZE_DIAG에 지정되어 있으면 검사는 사용 설정되지 않으며 진단 메시지도 제공되지 않습니다.

다음은 UBSan 런타임 라이브러리에서 제공하는 정보의 예시입니다.

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

LLVM 기반의 사용자 공간 구성요소 새니타이저와 마찬가지로 Android에는 Kernel Address Sanitizer(KASAN)가 포함되어 있습니다. KASAN은 커널 및 컴파일 시간 수정 기능을 결합하여 버그 탐색 및 근본 원인 분석을 단순화하는 계측 시스템을 제공합니다.

KASAN은 커널에서 여러 유형의 메모리 위반을 감지할 수 있습니다. 또한 스택, 힙 및 전역 변수에서의 범위를 벗어난 읽기 및 쓰기와 use-after-free 및 double free를 감지할 수 있습니다.

ASAN과 마찬가지로 KASAN은 런타임 시 메모리 액세스를 추적하기 위해 컴파일 시점의 메모리 함수 계측과 섀도 메모리의 조합을 사용합니다. KASAN에서 커널 메모리 공간의 8분의 1은 메모리 액세스가 유효한지 아닌지를 결정하는 섀도 메모리 전용입니다.

KASAN은 x86_64 및 arm64 아키텍처에서 지원됩니다. 4.0부터 업스트림 커널의 일부였으며 Android 3.18 기반 커널로 백포트되었습니다. KASAN은 4.9.2를 기반으로 하여 gcc로 컴파일된 Android 커널에서 테스트되었습니다.

KASAN 외에도 kcov는 테스트에 유용한 또 다른 커널 수정 기능입니다. kcov는 커널에서 커버리지 기반 퍼징 테스트를 지원하기 위해 개발되었습니다. 이는 syscall 입력 값으로 커버리지를 측정하며 syzkaller와 같은 퍼징 시스템에서 유용합니다.

구현

KASAN 및 kcov가 사용 설정된 커널을 컴파일하려면 커널 빌드 구성에 다음 빌드 플래그를 추가합니다.

CONFIG_KASAN
CONFIG_KASAN_INLINE
CONFIG_TEST_KASAN
CONFIG_KCOV
CONFIG_SLUB
CONFIG_SLUB_DEBUG
CONFIG_CC_OPTIMIZE_FOR_SIZE

그리고 다음을 삭제합니다.

CONFIG_SLUB_DEBUG_ON
CONFIG_SLUB_DEBUG_PANIC_ON
CONFIG_KASAN_OUTLINE
CONFIG_KERNEL_LZ4

그런 다음 평소와 같이 커널을 빌드하고 플래시합니다. KASAN 커널은 원래 커널보다 훨씬 큽니다. 가능한 경우 부트 매개변수와 부트로더 설정을 수정합니다.

커널을 플래시한 후 KASAN이 사용 설정되어 실행 중인지 커널 부트 로그를 확인합니다. 커널은 다음과 같이 KASAN의 메모리 맵 정보로 시작합니다.

...
[    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)
...

그리고 버그가 다음과 같이 표시됩니다.

[   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 ==================================================================

또한 커널에서 모듈을 사용 설정하는 경우 test_kasan 커널 모듈을 로드하여 추가로 테스트할 수 있습니다. 모듈은 범위를 벗어난 메모리 액세스 및 use-after-free를 시도하며, 타겟 기기에서 KASAN을 테스트하는 데 유용합니다.