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에는 일련의 새니타이저를 동시에 사용 설정할 수 있는 두 가지 손쉬운 방법으로 integer
및 default-ub
를 제공합니다. integer는 integer-divide-by-zero
, signed-integer-overflow
및 unsigned-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을 테스트하는 데 유용합니다.