← All reports

[libpas] Use tagged_bmalloc_heap for MTE allocations

Component: libpas / bmalloc allocator | c8cb35b

Source/bmalloc/bmalloc/bmalloc.cpp

void freeOutOfLine(void* object, HeapKind kind)
{
+ if (bmalloc_try_deallocate_casual(object))
+ return;
+ // ... hardening이 적용되지 않은 WebContent process에서는 도달하지 않을 것으로 예상됨
+ if (tagged_bmalloc_try_deallocate_inline_only(object))
+ return;
+ tagged_bmalloc_deallocate_casual(object);
}
+void* reallocOutOfLine(void* object, size_t newSize)
+{
+ if (tagged_bmalloc_owns_object(object))
+ return tagged_bmalloc_reallocate(object, newSize, pas_reallocate_free_if_successful);
+ return bmalloc_reallocate_inline(object, newSize, pas_reallocate_free_if_successful);
+}

Source/bmalloc/bmalloc/TZoneHeap.cpp

void tzoneFree(void* p)
{
- bmalloc_deallocate_inline(p);
+ api::free(p);
}

libpas는 bmalloc, FastMalloc, TZoneMalloc의 기반이 되는 allocator입니다. 지금까지는 하나의 pas_heap이 MTE tag가 붙는 allocation과 그렇지 않은 allocation을 모두 처리해야 했습니다. 또한 "compact" allocation, 즉 압축된 32-bit 형태로 표현할 수 있는 pointer는 별도의 megapage와 heap-ref를 필요로 했습니다.

ARM MTE 환경에서는 메모리에 4-bit tag가 붙고, 접근할 때마다 이 tag가 검사됩니다. free 시점에는 tag가 다시 무작위로 설정되므로 use-after-free와 overflow를 탐지할 수 있습니다. 문제는 모든 heap이 tagging을 수행할 수 있었다는 점입니다. 그 결과 tag 설정 코드가 곳곳에 inline되었고, heap마다 서로 다른 종류의 메모리를 mmap해야 했습니다.

이 commit은 그동안 컴파일만 되어 있고 사용되지 않던 tagged_bmalloc_heap을 활성화했습니다. 이제 MTE tag가 붙는 FastMalloc allocation은 이 heap에서 처리됩니다. 한편 compactness 처리는 libpas 바깥으로 옮겨졌습니다. pas_allocation_mode가 삭제되었고, compact/non-compact로 나뉘어 있던 megapage와 heap-ref도 하나로 통합되었습니다.

bmalloc.h는 tagged heap과 default heap 사이의 dispatch를 담당합니다. free 경로는 먼저 non-MTE를 가정해 해제를 시도하고, 이어서 MTE 여부를 확인합니다. 둘 다 해당하지 않으면 casual 경로로 넘어갑니다. Tag 설정 코드는 tagged heap 뒤편의 out-of-line 경로로 이동했습니다. 또한 tagging 여부는 allocator별 is_mte_tagged가 아니라 heap config에서 결정되도록 변경되었습니다. 이 밖에도 충분히 큰 compact 객체는 이제 libmalloc에 위임할 수 있게 되었습니다. small_medium_bootstrap heap 파일은 pas_mte_bootstrap_*로 이름이 변경되었습니다.

Allocation (bmalloc::api::malloc, mode = Compact | NonCompact):

  malloc(size, mode)
    ├─► Compact ──► bmalloc_heap
    └─► NonCompact
          ├─► MTE enabled (hardened processes), taggable size
          │     └─► tagged_bmalloc_heap (tag-set / retag out of line)
          └─► MTE disabled, or above tagging ceiling
                └─► bmalloc_heap (no tag code inlined; WebContent fast path)

Free (bmalloc::api::free):
  Before: bmalloc_deallocate_inline (one heap)
  After:  1. inline-only bmalloc_heap free (small-segregated, TLC present, non-MTE) -- unchanged fast path
          else freeOutOfLine:
          2. bmalloc_try_deallocate_casual
          3. tagged_bmalloc_try_deallocate_inline_only
          4. tagged_bmalloc_deallocate_casual

MTE가 활성화된 프로세스에서는 non-compact allocation이 이제 별도의 tagged heap에서 할당됩니다. 이에 따라 tagging, 해제, 메모리 배치 방식이 달라지고, 그 배치를 전제로 한 heap grooming 및 MTE 우회 가정도 함께 달라집니다. Tag 설정 코드가 out-of-line으로 빠지면서 default heap의 inline fastMalloc은 141개에서 73개 instruction으로 줄었습니다. 반면 fastFree는 44개에서 45개로 소폭 늘었습니다. non-MTE WebContent fast path는 의도적으로 거의 그대로 유지되었습니다.

이번 변경은 allocate, free, realloc, large heap 경로의 핵심부를 건드리는 대규모 allocator 재작성입니다. 이 과정에서 exploit mitigation이 의존하는 불변 조건도 함께 바뀌었습니다.