[libpas] Use tagged_bmalloc_heap for MTE allocations
Component: libpas / bmalloc allocator | c8cb35b
Source/bmalloc/bmalloc/bmalloc.cpp
Source/bmalloc/bmalloc/TZoneHeap.cpp
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
Significance
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는 의도적으로 거의 그대로 유지되었습니다.
Audit directions
이번 변경은 allocate, free, realloc, large heap 경로의 핵심부를 건드리는 대규모 allocator 재작성입니다. 이 과정에서 exploit mitigation이 의존하는 불변 조건도 함께 바뀌었습니다.
- Free 경로 dispatch. 새 순서는 non-MTE를 가정한 시도, 이어서 MTE, 마지막으로 casual 경로입니다. 먼저 pointer가 잘못된 heap으로 전달되는 경우가 있는지 살펴볼 필요가 있습니다. tagged heap과 default heap 사이에서 double-free나 type confusion이 발생할 가능성도 점검 대상입니다. 또한
isMTEEnabledUnsafefast check가 race에 노출되거나 오래된 값을 참조하는 경우도 확인해야 합니다. pas_large_heap. commit 설명에 따르면, heap이 소유하지 않은 entry는 제거해서는 안 됩니다. 그렇지 않으면 try-deallocate가 실패한 뒤 이후의 free가 해당 entry를 찾지 못하게 됩니다. 새로 추가된try_deallocate와 delegation 경로를 살펴볼 필요가 있습니다.large_object_delegation_is_enabled와should_delegate_user_allocation_to_system_malloc로직도 lifetime 오류나 metadata corruption 관점에서 점검해야 합니다.- libmalloc 위임. 이제 큰 compact 객체도 위임 대상에 포함됩니다. libpas와 libmalloc 사이에서 ownership,
owns_object, realloc이 어떻게 동작하는지 확인해야 합니다. 이 변경이 heap grooming과 layout 예측 가능성에 미치는 영향도 함께 살펴볼 필요가 있습니다. - Heap config 기반 tagging 결정.
pas_mte_maybe_tag_allocated_region,pas_mte_retag_freed_region_if_tagged, 그리고 인접 tag 배제 로직을 점검해야 합니다. 핵심은 객체가 tag 없이 남거나 tag가 이중으로 설정되는 경우가 있는지입니다. 특히 JSC Structure block은 alignment에 민감한 보조 allocation을 사용하므로 우선적으로 살펴볼 필요가 있습니다. - Compact / non-compact 구분 제거. megapage가 통합되면서 주소 범위에 대한 가정이 달라졌습니다. compact pointer encoding, enumeration(
testEnumerationInvalidCompactHeapBump가 수정되었습니다), reallocate의 copy 경로를 확인해야 합니다. 한편 allocation mode 제거는 여러 함수 signature에 걸쳐 기계적으로 적용되었습니다. 따라서 flex, iso, PGM, bitfit 호출 지점 가운데 이전과 다른 기본값을 받게 된 곳이 있는지 찾아볼 필요가 있습니다.