MTE 보고서 이해하기

SIGSEGV가 다운되면서 표시되는 코드 9(SEGV_MTESERR), 코드 8(SEGV_MTEAERR)은 메모리 태그 오류입니다. Memory Tagging Extension(MTE)은 Android 12 이상에서 지원되는 Armv9 기능입니다. MTE는 태그된 메모리의 하드웨어 구현입니다. 메모리 안전 버그를 감지하고 완화하는 세분화된 메모리 보호 기능이 있습니다.

C/C++에서 malloc(), new() 연산자 또는 이와 유사한 함수에 대한 호출로부터 반환된 포인터는 할당 범위 내의 메모리에 액세스하는 데만 사용될 수 있으며, 메모리 할당이 활성화된 상태(해제 또는 삭제되지 않음)에서만 가능합니다. Android에서 MTE는 이 규칙의 위반을 감지하기 위해 사용되며, 비정상 종료 보고서에는 위반 내용이 'Buffer Overflow'/'Buffer Underflow'/'Use After Free' 오류로 표시됩니다.

MTE에는 동기('sync') 모드와 비동기('async') 모드가 있습니다. 동기 모드는 느리지만 더 정확한 진단 결과를 보여줍니다. 비동기 모드는 빠르지만 대략적인 진단 결과만 보여줄 수 있습니다. 진단 결과가 다소 다르기 때문에 두 가지 모드를 나누어 설명하겠습니다.

동기 모드 MTE

MTE의 동기('sync') 모드에서는 SIGSEGV가 code 9(SEGV_MTESERR) 메시지를 표시하며 다운됩니다.

pid: 13935, tid: 13935, name: sanitizer-statu  >>> sanitizer-status <<<
uid: 0
tagged_addr_ctrl: 000000000007fff3
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x800007ae92853a0
Cause: [MTE]: Use After Free, 0 bytes into a 32-byte allocation at 0x7ae92853a0
x0  0000007cd94227cc  x1  0000007cd94227cc  x2  ffffffffffffffd0  x3  0000007fe81919c0
x4  0000007fe8191a10  x5  0000000000000004  x6  0000005400000051  x7  0000008700000021
x8  0800007ae92853a0  x9  0000000000000000  x10 0000007ae9285000  x11 0000000000000030
x12 000000000000000d  x13 0000007cd941c858  x14 0000000000000054  x15 0000000000000000
x16 0000007cd940c0c8  x17 0000007cd93a1030  x18 0000007cdcac6000  x19 0000007fe8191c78
x20 0000005800eee5c4  x21 0000007fe8191c90  x22 0000000000000002  x23 0000000000000000
x24 0000000000000000  x25 0000000000000000  x26 0000000000000000  x27 0000000000000000
x28 0000000000000000  x29 0000007fe8191b70
lr  0000005800eee0bc  sp  0000007fe8191b60  pc  0000005800eee0c0  pst 0000000060001000

backtrace:
      #00 pc 00000000000010c0  /system/bin/sanitizer-status (test_crash_malloc_uaf()+40) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #01 pc 00000000000014a4  /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #02 pc 00000000000019cc  /system/bin/sanitizer-status (main+1032) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #03 pc 00000000000487d8  /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+96) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)

deallocated by thread 13935:
      #00 pc 000000000004643c  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::quarantineOrDeallocateChunk(scudo::Options, void*, scudo::Chunk::UnpackedHeader*, unsigned long)+688) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #01 pc 00000000000421e4  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+212) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #02 pc 00000000000010b8  /system/bin/sanitizer-status (test_crash_malloc_uaf()+32) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #03 pc 00000000000014a4  /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)

allocated by thread 13935:
      #00 pc 0000000000042020  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::allocate(unsigned long, scudo::Chunk::Origin, unsigned long, bool)+1300) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #01 pc 0000000000042394  /apex/com.android.runtime/lib64/bionic/libc.so (scudo_malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #02 pc 000000000003cc9c  /apex/com.android.runtime/lib64/bionic/libc.so (malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #03 pc 00000000000010ac  /system/bin/sanitizer-status (test_crash_malloc_uaf()+20) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #04 pc 00000000000014a4  /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)

MTE 비정상 종료 보고서에는 문제가 감지된 지점의 레지스터 덤프와 backtrace 정보가 있습니다. MTE가 감지한 오류의 'Cause:' 행에는 위 예와 같이 '[MTE]' 표시와 자세한 내용이 나타납니다. 이때 감지된 오류 유형은 'Use after free'이며 '0 bytes into a 32-byte allocation at 0x7ae92853a0'는 할당의 크기와 주소, 액세스를 시도한 할당에 관한 오프셋을 나타냅니다.

MTE 비정상 종료 보고서에는 감지 지점 외의 추가적인 backtrace 정보도 포함됩니다.

'Use After Free' 오류의 경우 크래시 덤프에 'deallocated by', 'allocated by' 섹션이 추가되며, 이러한 섹션은 메모리 할당 해제 시점(사용 전)과 이전 할당 시점의 스택 트레이스 정보를 보여줍니다. 어떤 스레드에 의해 할당/할당 해제되었는지도 보여줍니다. 간단한 이번 사례에서는 감지 스레드, 할당 스레드, 할당 해제 스레드 모두 동일하지만 더 복잡한 실제 사례에서는 그렇지 않을 수 있습니다. 스레드가 다르다는 것을 숙지하면 동시 실행 관련 버그를 찾는 데 큰 도움이 됩니다.

'Buffer Overflow', 'Buffer Underflow' 오류는 'allocated by' 스택 트레이스만 추가로 보여줍니다. 정의에 따르면 아직 할당 해제되지 않았기 때문입니다. 해제되었다면 'Use After Free' 오류로 나올 것입니다.

Cause: [MTE]: Buffer Overflow, 0 bytes right of a 32-byte allocation at 0x7ae92853a0
[...]
backtrace:
[...]
allocated by thread 13949:

단어 'right'는 할당 끝에서 몇 바이트를 지나 잘못된 액세스가 발생했는지를 뜻합니다. 언더플로의 경우 'left'라고 표시되며 이는 할당 시작 전에 잘못된 액세스가 발생한 바이트 수를 뜻합니다.

후보 원인

SEGV_MTESERR 보고서에 다음 행이 포함되는 경우가 있습니다.

Note: multiple potential causes for this crash were detected, listing them in decreasing order of likelihood.

이는 오류 출처로 유력한 후보가 여러 개 있는데 어떤 원인이 실제 원인인지 알 수 없는 경우에 발생합니다. 사용자의 분석을 위해 실제 원인일 가능성 순으로 후보가 3개까지 표시됩니다.

signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x400007b43063db5
backtrace:
    [stack...]

Note: multiple potential causes for this crash were detected, listing them in decreasing order of probability.

Cause: [MTE]: Use After Free, 5 bytes into a 10-byte allocation at 0x7b43063db0
deallocated by thread 6663:
    [stack...]
allocated by thread 6663:
    [stack...]

Cause: [MTE]: Use After Free, 5 bytes into a 6-byte allocation at 0x7b43063db0
deallocated by thread 6663:
    [stack...]

allocated by thread 6663:
    [stack...]

위 예시는 잘못된 메모리 액세스의 의도한 대상이었을 수 있는 동일한 메모리 주소에서 감지된 두 건의 최근 할당을 보여줍니다. 이는 할당에서 사용 가능한 메모리를 재사용할 때 발생할 수 있습니다. 예를 들어 new, free, new, free, new, free, access와 같은 시퀀스가 있다면 일어날 수 있습니다. 최근 할당 기록이 먼저 나타납니다.

상세 원인 판단 휴리스틱

비정상 종료의 'Cause' 행은 액세스된 포인터의 출처인 메모리 할당을 보여주어야 합니다. 안타깝지만 MTE 하드웨어로는 일치하지 않은 태그를 포함하는 포인터를 할당으로 변환할 수 없습니다. SEGV_MTESERR 비정상 종료의 원인을 찾기 위해 Android에서 다음 데이터를 분석합니다.

  • 오류 주소(포인터 태그 포함).
  • 스택 트레이스를 포함한 힙 할당 및 메모리 태그의 최근 목록
  • 현재 활성 상태인 주변 할당과 할당의 메모리 태그

메모리 태그와 오류 주소 태그가 일치하는 오류 주소에서 최근에 할당 해제된 메모리가 'Use After Free' 오류의 원인일 수 있습니다.

메모리 태그와 오류 주소 태그가 일치하는 주변 활성 메모리가 'Buffer Overflow'(또는 'Buffer Underflow')의 원인일 수 있습니다.

시공간상으로 오류와 이웃해 있는 할당은 멀리 있는 할당보다 오류의 원인일 가능성이 큽니다.

할당 해제된 메모리는 재사용되는 경우가 많고 태그 값의 종류가 적으므로(16가지 미만) 오류 원인의 후보는 여러 개가 발견될 수 있으며 실제 원인을 자동으로 찾는 방법은 없습니다. 이러한 이유로 MTE 보고서에는 잠재적인 원인이 여러 개 표시될 수 있습니다.

앱 개발자는 오류를 일으켰을 가능성이 가장 큰 원인부터 살펴보는 것이 좋습니다. 스택 트레이스를 기준으로 하면 관련 없는 원인을 쉽게 필터링할 수 있습니다.

비동기 모드 MTE

MTE의 비동기('async') 모드에서는 SIGSEGV가 code 8(SEGV_MTEAERR) 메시지를 표시하며 다운됩니다.

SEGV_MTEAERR 오류는 프로그램이 잘못된 메모리 액세스를 실행하자마자 발생하지 않습니다. 해당 이벤트 발생 직후 문제가 감지되며 이 시점에 프로그램이 종료됩니다. 일반적으로 다음 시스템 호출 시 프로그램이 종료되지만 타이머 중단과 같이 userspace-to-kernel 전환 시점에 발생할 수도 있습니다.

SEGV_MTEAERR 오류는 메모리 주소를 저장하지 않습니다(항상 '-------'로 표시). 이 오류의 backtrace는 조건이 감지된 순간(즉, 다음 시스템 호출이나 기타 컨텍스트 전환)에 발생하며, 잘못된 액세스가 수행될 때는 발생하지 않습니다.

즉, 비동기 모드 MTE 비정상 종료 보고서의 '기본' backtrace는 일반적으로 관련성이 없습니다. 따라서 비동기 모드 오류는 동기 모드 오류보다 디버그하기 훨씬 어렵습니다. 이러한 오류는 스레드 주변 코드에서의 메모리 버그 유무를 알려주는 것으로 이해할 수 있습니다. Tombstone 파일 하단의 로그를 통해 실제 발생 상황에 관한 단서를 얻을 수 있습니다. 또는 동기 모드에서 오류를 재현하고 동기 모드에서 제공하는 상세 진단 결과를 사용하면 됩니다.

고급 주제

메모리 태깅이란 힙 할당마다 4비트(0..15) 태그 값을 임의로 할당하는 것을 말합니다. 이 값은 할당된 힙 메모리에 대응하는 특수 메타데이터 영역에 저장됩니다. malloc(), new() 연산자 등 함수에서 반환된 힙 포인터의 최상위 바이트에 동일한 값이 할당됩니다.

프로세스에서 태그 검사가 사용 설정된 경우 CPU는 포인터의 최상위 바이트를 각 메모리 액세스의 메모리 태그와 자동으로 비교합니다. 태그가 일치하지 않으면 CPU에서 오류 신호를 보내며 비정상 종료가 발생합니다.

가능한 태그 값이 제한되어 있으므로 이 방법은 확률의 영향을 받습니다. 특정 포인터가 액세스하면 안 되는 메모리 위치(예: 범위 밖, 할당 해제 후('댕글링 포인터'))는 다른 태그 값을 가지며 비정상 종료를 일으킬 가능성이 높습니다. 버그 발생을 하나도 감지하지 못할 확률은 약 7%입니다. 태그 값은 무작위로 할당되므로 다음으로 발생하는 버그를 감지할 확률은 약 93%입니다.

태그 값은 아래 강조 표시된 것처럼 오류 주소 필드와 레지스터 덤프에서 확인할 수 있습니다. 이 섹션을 통해 태그가 정상적으로 설정되어 있는지 검토할 수 있고 동일한 태그 값을 갖는 다른 주변 메모리 할당도 확인할 수 있습니다. 이러한 메모리 할당은 보고서에서 나열된 할당과 함께 오류의 원인일 수 있습니다. 이는 개발자보다는 MTE나 하위 시스템 구성요소를 구현하려는 작업자에게 도움이 될 것으로 예상됩니다.

signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x0800007ae92853a0
Cause: [MTE]: Use After Free, 0 bytes into a 32-byte allocation at 0x7ae92853a0
    x0  0000007cd94227cc  x1  0000007cd94227cc  x2  ffffffffffffffd0  x3  0000007fe81919c0
    x4  0000007fe8191a10  x5  0000000000000004  x6  0000005400000051  x7  0000008700000021
    x8  0800007ae92853a0  x9  0000000000000000  x10 0000007ae9285000  x11 0000000000000030
    x12 000000000000000d  x13 0000007cd941c858  x14 0000000000000054  x15 0000000000000000
    x16 0000007cd940c0c8  x17 0000007cd93a1030  x18 0000007cdcac6000  x19 0000007fe8191c78
    x20 0000005800eee5c4  x21 0000007fe8191c90  x22 0000000000000002  x23 0000000000000000
    x24 0000000000000000  x25 0000000000000000  x26 0000000000000000  x27 0000000000000000
    x28 0000000000000000  x29 0000007fe8191b70
    lr  0000005800eee0bc  sp  0000007fe8191b60  pc  0000005800eee0c0  pst 0000000060001000

비정상 종료 보고서에는 따로 'Memory tags' 섹션도 표시되는데, 이를 통해 오류 주소 주변의 메모리 태그 정보를 확인할 수 있습니다. 아래 예시에서는 포인터 태그 '4'가 메모리 태그 'a'와 일치하지 않습니다.

Memory tags around the fault address (0x0400007b43063db5), one tag per 16 bytes:
  0x7b43063500: 0  f  0  2  0  f  0  a  0  7  0  8  0  7  0  e
  0x7b43063600: 0  9  0  8  0  5  0  e  0  f  0  c  0  f  0  4
  0x7b43063700: 0  b  0  c  0  b  0  2  0  1  0  4  0  7  0  8
  0x7b43063800: 0  b  0  c  0  3  0  a  0  3  0  6  0  b  0  a
  0x7b43063900: 0  3  0  4  0  f  0  c  0  3  0  e  0  0  0  c
  0x7b43063a00: 0  3  0  2  0  1  0  8  0  9  0  4  0  3  0  4
  0x7b43063b00: 0  5  0  2  0  5  0  a  0  d  0  6  0  d  0  2
  0x7b43063c00: 0  3  0  e  0  f  0  a  0  0  0  0  0  0  0  4
=>0x7b43063d00: 0  0  0  a  0  0  0  e  0  d  0 [a] 0  f  0  e
  0x7b43063e00: 0  7  0  c  0  9  0  a  0  d  0  2  0  0  0  c
  0x7b43063f00: 0  0  0  6  0  b  0  8  0  3  0  0  0  5  0  e
  0x7b43064000: 0  d  0  2  0  7  0  a  0  7  0  a  0  d  0  8
  0x7b43064100: 0  b  0  2  0  b  0  4  0  1  0  6  0  d  0  4
  0x7b43064200: 0  1  0  6  0  f  0  2  0  f  0  6  0  5  0  c
  0x7b43064300: 0  1  0  4  0  d  0  6  0  f  0  e  0  1  0  8
  0x7b43064400: 0  f  0  4  0  3  0  2  0  1  0  2  0  5  0  6

모든 레지스터 값 주변의 메모리 콘텐츠를 보여주는 Tombstone 섹션에서는 태그 값도 표시됩니다.

memory near x10 ([anon:scudo:primary]):
0000007b4304a000 7e82000000008101 000003e9ce8b53a0  .......~.S......
0700007b4304a010 0000200000006001 0000000000000000  .`... ..........
0000007b4304a020 7c03000000010101 000003e97c61071e  .......|..a|....
0200007b4304a030 0c00007b4304a270 0000007ddc4fedf8  p..C{.....O.}...
0000007b4304a040 84e6000000008101 000003e906f7a9da  ................
0300007b4304a050 ffffffff00000042 0000000000000000  B...............
0000007b4304a060 8667000000010101 000003e9ea858f9e  ......g.........
0400007b4304a070 0000000100000001 0000000200000002  ................
0000007b4304a080 f5f8000000010101 000003e98a13108b  ................
0300007b4304a090 0000007dd327c420 0600007b4304a2b0   .'.}......C{...
0000007b4304a0a0 88ca000000010101 000003e93e5e5ac5  .........Z^>....
0a00007b4304a0b0 0000007dcc4bc500 0300007b7304cb10  ..K.}......s{...
0000007b4304a0c0 0f9c000000010101 000003e9e1602280  ........."`.....
0900007b4304a0d0 0000007dd327c780 0700007b7304e2d0  ..'.}......s{...
0000007b4304a0e0 0d1d000000008101 000003e906083603  .........6......
0a00007b4304a0f0 0000007dd327c3b8 0000000000000000  ..'.}...........