CVE-2025-0072- GitHub 블로그로 MTE를 우회합니다
메모리 태깅 확장 (MTE)은 메모리 손상 취약점을 거의 불가능하게 만드는 고급 메모리 안전 기능입니다. 그러나 완화는 완전히 밀폐되어 있지 않습니다.
작년에 나는 ARM의 Mali GPU 드라이버의 취약성 인 CVE-2023-6241에 대해 썼으며, 이로 인해 신뢰할 수없는 Android 앱이 MTE를 우회하고 임의의 커널 코드 실행을 얻을 수있었습니다. 이 게시물에서는 CVE-2025-0072를 살펴볼 것입니다. 이전과 마찬가지로 악의적 인 Android 앱이 MTE를 우회하고 임의의 커널 코드 실행을 얻을 수 있습니다.
나는 2024 년 12 월 12 일 에이 문제를 무기 할 것으로보고했다. 그것은 2025 년 5 월 2 일에 공개적으로 발표되었으며 2025 년 5 월 2025 년 5 월 보안 업데이트에 포함 된 Mali Driver 버전 R54P0에 고정되었다. 취약점은 Google의 픽셀 7, 8 및 9 시리즈와 같은 CSF (Command Stream Frontend) 아키텍처를 사용하는 최신 ARM Mali GPU의 장치에 영향을 미칩니다. 커널 MTE가 활성화 된 픽셀 8의 악용을 개발하고 테스트했으며, 7과 9에서는 약간의 수정이 있어야한다고 생각합니다.
다음은 CSF 대기열의 작동 방식,이 버그를 악용하는 데 사용한 단계 및 궁극적으로 MTE 보호를 우회하여 커널 코드 실행을 달성하는 방법에 대한 깊은 다이빙입니다.
CSF 대기열의 작동 방식과 위험 해지는 방법
CSF 기능이 장착 된 ARM Mali GPU는 Driver에서 구현 된 명령 대기열을 통해 Userland 응용 프로그램과 통신합니다. kbase_queue
사물. 대기열은 사용하여 생성됩니다 KBASE_IOCTL_CS_QUEUE_REGISTER
ioctl
. 사용하려면 kbase_queue
그것은 생성되며, 먼저 kbase_queue_group
그것은 KBASE_IOCTL_CS_QUEUE_GROUP_CREATE ioctl
. 에이 kbase_queue
a에 묶일 수 있습니다 kbase_queue_group
함께 KBASE_IOCTL_CS_QUEUE_BIND ioctl
. 바인딩 할 때 a kbase_queue
a kbase_queue_group
손잡이가 생성됩니다 get_user_pages_mmap_handle
사용자 응용 프로그램으로 돌아 왔습니다.
int kbase_csf_queue_bind(struct kbase_context *kctx, union kbase_ioctl_cs_queue_bind *bind)
{
...
group = find_queue_group(kctx, bind->in.group_handle);
queue = find_queue(kctx, bind->in.buffer_gpu_addr);
…
ret = get_user_pages_mmap_handle(kctx, queue);
if (ret)
goto out;
bind->out.mmap_handle = queue->handle;
group->bound_queues[bind->in.csi_index] = queue;
queue->group = group;
queue->group_priority = group->priority;
queue->csi_index = (s8)bind->in.csi_index;
queue->bind_state = KBASE_CSF_QUEUE_BIND_IN_PROGRESS;
out:
rt_mutex_unlock(&kctx->csf.lock);
return ret;
}
또한 상호 참조는 kbase_queue_group
그리고 queue
. 통화가 완료되면 queue->bind_state
설정되었습니다 KBASE_CSF_QUEUE_BIND_IN_PROGRESS
결합이 완료되지 않았 음을 나타냅니다. 바인딩을 완료하려면 사용자 응용 프로그램이 호출해야합니다 mmap
손잡이가 ioctl
파일 오프셋으로 이것 mmap
전화는 처리됩니다 kbase_csf_cpu_mmap_user_io_pages
GPU 메모리를 통해 할당됩니다 kbase_csf_alloc_command_stream_user_pages
그리고 그것을 사용자 공간에지도합니다.
int kbase_csf_alloc_command_stream_user_pages(struct kbase_context *kctx, struct kbase_queue *queue)
{
struct kbase_device *kbdev = kctx->kbdev;
int ret;
lockdep_assert_held(&kctx->csf.lock);
ret = kbase_mem_pool_alloc_pages(&kctx->mem_pools.small[KBASE_MEM_GROUP_CSF_IO],
KBASEP_NUM_CS_USER_IO_PAGES, queue->phys, false, //task);
...
ret = kernel_map_user_io_pages(kctx, queue);
...
get_queue(queue);
queue->bind_state = KBASE_CSF_QUEUE_BOUND;
mutex_unlock(&kbdev->csf.reg_lock);
return 0;
...
}
1에서. 위의 스 니펫에서 kbase_mem_pool_alloc_pages
GPU 메모리 풀에서 메모리 페이지를 할당하도록 호출되며 주소는 주소가 저장됩니다. queue->phys
필드. 그런 다음이 페이지는 사용자 공간에 매핑됩니다. bind_state
대기열의 설정이 설정됩니다 KBASE_CSF_QUEUE_BOUND
. 이 페이지는 Mmapped 영역이 사용자 공간에서 맵핑되지 않은 경우에만 해제됩니다. 이 경우 kbase_csf_free_command_stream_user_pages
페이지를 자유롭게 해제하도록 요청됩니다 kbase_mem_pool_free_pages
.
void kbase_csf_free_command_stream_user_pages(struct kbase_context *kctx, struct kbase_queue *queue)
{
kernel_unmap_user_io_pages(kctx, queue);
kbase_mem_pool_free_pages(&kctx->mem_pools.small[KBASE_MEM_GROUP_CSF_IO],
KBASEP_NUM_CS_USER_IO_PAGES, queue->phys, true, false);
...
}
이것은 페이지가 저장된 경우에 해당합니다 queue->phys
그리고 이것은 페이지가 사용자 공간에서 맵핑되지 않은 경우에만 발생하기 때문에 페이지가 해제 된 후에 액세스하는 것을 방지합니다.
악용 아이디어
흥미로운 부분은 우리가 물을 때 시작됩니다. 수정할 수 있다면 어떻게되는지 queue->phys
사용자 공간에 매핑 한 후. 예를 들어, 트리거 할 수있는 경우 kbase_csf_alloc_command_user_pages
다시 새 페이지를 덮어 쓸 수 있습니다 queue->phys
그리고 그것들을 사용자 공간에 매핑 한 다음 이전에 맵핑 된 영역을 맵핑하고 kbase_csf_free_command_stream_user_pages
페이지를 자유롭게하기 위해 호출됩니다 queue->phys
. 그러나 왜냐하면 queue->phys
새로 할당 된 페이지로 덮어 쓰고, 나는 오래된 지역을 마무리하면서 새 페이지를 석방하는 상황에 빠졌습니다.

위의 그림에서 오른쪽 열은 사용자 공간의 매핑이며 녹색 사각형은 매핑되고 회색 사각형은 맵핑되었습니다. 왼쪽 열은 백업 페이지에 저장되어 있습니다 queue->phys
. 새로운 queue->phys
현재 저장된 페이지입니다 queue->phys
늙었지만 queue->phys
이전에 저장되었지만 새 페이지로 대체되는 페이지입니다. 녹색은 페이지가 살아 있고 빨간색이 해제되었음을 나타냅니다. 덮어 쓰기 후 queue->phys
그리고 오래된 지역, 새로운 queue->phys
대신 새 사용자 지역에 매핑되는 동안 대신 해방됩니다. 이것은 사용자 공간이 해방 된 신규에 액세스 할 수 있음을 의미합니다. queue->phys
페이지. 이것은 나에게 페이지가없는 취약점을 제공합니다.
취약성
이 상황을 달성하는 방법을 살펴 보겠습니다. 시도해야 할 첫 번째 명백한 것은 내가 묶을 수 있는지 확인하는 것입니다. kbase_queue
여러 번 사용합니다 KBASE_IOCTL_CS_QUEUE_BIND ioctl
. 그러나 이것은 불가능합니다 queue->group
바인딩 전에 필드를 점검합니다.
int kbase_csf_queue_bind(struct kbase_context *kctx, union kbase_ioctl_cs_queue_bind *bind)
{
...
if (queue->group || group->bound_queues[bind->in.csi_index])
goto out;
...
}
후 kbase_queue
묶여 있습니다 queue->group
로 설정됩니다 kbase_queue_group
그것이 바인딩되는 것은 그것을 방지합니다 kbase_queue
다시 바인딩에서. 또한, 한 번 a kbase_queue
구속력이 있습니다 ioctl
. 종료 될 수 있습니다 KBASE_IOCTL_CS_QUEUE_TERMINATE
그러나 그것은 또한 삭제 될 것입니다 kbase_queue
. 따라서 대기열에서 리딩하는 것이 불가능하다면, kbase_queue_group
? 예를 들어, a kbase_queue_group
The와 함께 종료됩니다 KBASE_IOCTL_CS_QUEUE_GROUP_TERMINATE ioctl
? 언제 a kbase_queue_group
정리 과정의 일부로 종료됩니다. kbase_csf_term_descheduled_queue_group
다음과 같은 대기열을 해제하기 위해
void kbase_csf_term_descheduled_queue_group(struct kbase_queue_group *group)
{
...
for (i = 0; i bound_queues[i];
/* The group is already being evicted from the scheduler */
if (queue)
unbind_stopped_queue(kctx, queue);
}
...
}
그런 다음이를 재설정합니다 queue->group
의장 kbase_queue
결점이 없습니다 :
static void unbind_stopped_queue(struct kbase_context *kctx, struct kbase_queue *queue)
{
...
if (queue->bind_state != KBASE_CSF_QUEUE_UNBOUND) {
...
queue->group->bound_queues[queue->csi_index] = NULL;
queue->group = NULL;
...
queue->bind_state = KBASE_CSF_QUEUE_UNBOUND;
}
}
특히, 이것은 이제 그것을 허용합니다 kbase_queue
다른 사람에게 묶는 것 kbase_queue_group
. 즉, 이제 다음 단계로 무료로 사용하는 페이지를 만들 수 있습니다.
- a
kbase_queue
그리고 akbase_queue_group
그런 다음 묶습니다kbase_queue
에kbase_queue_group
. - 사용자 IO 페이지의 GPU 메모리 페이지 생성
kbase_queue
a를 사용하여 사용자 공간에 매핑하십시오mmap
부르다. 이 페이지는 다음에 저장됩니다queue->phys
의장kbase_queue
. - 종료하십시오
kbase_queue_group
또한kbase_queue
. - 다른 것을 만듭니다
kbase_queue_group
그리고 묶음kbase_queue
이 새로운 그룹에. - 사용자 IO 페이지에 대한 새 GPU 메모리 페이지 생성
kbase_queue
그리고 그것들을 사용자 공간에 매핑하십시오. 이 페이지는 이제 기존 페이지를 덮어 씁니다queue->phys
. - 2 단계에 맵핑 된 사용자 공간 메모리를 맵핑 한 다음 페이지가 해방됩니다.
queue->phys
그러나 2 단계에서 생성 된 사용자 공간 매핑을 제거하지만 해제되는 페이지는 이제 5 단계에서 생성되고 맵핑되는 메모리 페이지가 여전히 사용자 공간에 맵핑되었습니다.
이는 특히 위의 6 단계에서 제출 된 페이지가 여전히 사용자 애플리케이션에서 액세스 할 수 있음을 의미합니다. 이전에 사용한 기술을 사용하여 Mali GPU의 PGD (Page Table Global Directories) 로이 무료 페이지를 재사용 할 수 있습니다.
요약하려면 kbase_va_region
할당됩니다. a의 후원 저장소에 페이지를 할당 할 때 kbase_va_region
그만큼 kbase_mem_pool_alloc_pages
기능이 사용됩니다.
int kbase_mem_pool_alloc_pages(struct kbase_mem_pool *pool, size_t nr_4k_pages,
struct tagged_addr *pages, bool partial_allowed)
{
...
/* Get pages from this pool */
while (nr_from_pool--) {
p = kbase_mem_pool_remove_locked(pool); //next_pool) {
/* Allocate via next pool */
err = kbase_mem_pool_alloc_pages(pool->next_pool, //
입력 인수 kbase_mem_pool
GPU 메모리를 할당하는 데 사용되는 드라이버 파일과 관련된 KBase_Context 객체가 관리하는 메모리 풀입니다. 의견에서 알 수 있듯이 할당은 실제로 계층으로 수행됩니다. 먼저 페이지가 현재에서 할당됩니다 kbase_mem_pool
사용 kbase_mem_pool_remove_locked
(위의 1). 전류에 용량이 충분하지 않은 경우 kbase_mem_pool
그러면 요청을 충족시키기 위해 pool->next_pool
페이지를 할당하는 데 사용됩니다 (위의 2). 심지어 pool->next_pool
그러면 용량이 없습니다 kbase_mem_alloc_page
버디 할당 자 (커널의 페이지 할당 자)를 통해 커널에서 직접 페이지를 할당하는 데 사용됩니다.
페이지를 제거하면 반대 방향으로 동일하게 발생합니다. kbase_mem_pool_free_pages
먼저 페이지를 kbase_mem_pool
현재의 kbase_context
메모리 풀이 가득 차면 나머지 페이지를 pool->next_pool
. 다음 수영장도 가득 차면 나머지 페이지는 버디 할당기를 통해 해방하여 커널로 반환됩니다.
내 게시물“메모리 부패없이 메모리 부패”에서 언급했듯이, pool->next_pool
Mali 드라이버가 관리하고 모든 KBase_Context가 공유하는 메모리 풀입니다. 또한 GPU 컨텍스트에서 사용하는 페이지 테이블 글로벌 디렉토리 (PGD)를 할당하는 데 사용됩니다. 특히 이것은 메모리 풀을주의 깊게 배열함으로써 kbase_va_region
GPU 컨텍스트의 PGD로 재사용됩니다. (이를 달성하는 방법에 대한 세부 사항을 읽으십시오.)
해제 된 페이지가 GPU 컨텍스트의 PGD로 재사용되면 사용자 공간 매핑을 사용하여 GPU에서 PGD를 다시 작성할 수 있습니다. 이를 통해 커널 코드를 포함한 커널 메모리를 GPU에 매핑 할 수 있으므로 커널 코드를 다시 작성하여 임의의 커널 코드를 실행할 수 있습니다. 또한 임의의 커널 데이터를 읽고 쓸 수 있으므로 프로세스의 자격 증명을 쉽게 다시 작성하여 루트를 얻고 Selinux를 비활성화 할 수 있습니다.
일부 설정 노트가있는 Pixel 8의 악용을 참조하십시오.
이것은 MTE를 어떻게 우회합니까?
마무리하기 전에이 악용이 왜 메모리 태그 확장 (MTE)을 우회하는 이유를 살펴 보겠습니다.
메모리 태깅 확장 (MTE)은 하드웨어 구현을 사용하여 메모리 손상을 확인하는 최신 암 프로세서의 보안 기능입니다.
ARM64 아키텍처는 64 비트 포인터를 사용하여 메모리에 액세스하는 반면, 대부분의 응용 프로그램은 훨씬 작은 주소 공간 (예 : 39, 48 또는 52 비트)을 사용합니다. 64 비트 포인터에서 가장 높은 비트는 실제로 사용되지 않습니다. 메모리 태깅의 주요 아이디어는 주소에 이러한 높은 비트를 사용하여 “태그”를 저장하는 것입니다. 그런 다음 주소와 관련된 메모리 블록에 저장된 다른 태그를 확인하는 데 사용할 수 있습니다.
선형 오버 플로우가 발생하고 포인터가 인접한 메모리 블록을 해석하는 데 사용되는 경우 포인터의 태그는 인접한 메모리 블록의 태그와 다를 수 있습니다. 이러한 불일치 시간에 이러한 태그를 확인함으로써, 그러한 불일치, 따라서 손상된 단절이 감지 될 수있다. 사용하지 않는 유형 메모리 손상의 경우, 메모리 블록의 태그가 풀릴 때마다 지워지고 할당 될 때 새 태그가 재 할당되면, 이미 해제되고 재생 된 객체를 비교하면 포인터 태그와 메모리의 태그 사이의 불일치로 이어질 수 있습니다.

메모리 태깅 확장은 ARM 아키텍처의 v8.5A 버전에 도입 된 명령 세트로 하드웨어로 메모리 태깅 및 점검 프로세스를 가속화합니다. 따라서 실제 애플리케이션에서 메모리 태깅을 사용할 수 있습니다. 하드웨어 가속 지침을 사용할 수있는 아키텍처에서 메모리 태그 지침을 호출하려면 메모리 할당 자의 소프트웨어 지원이 여전히 필요합니다. Linux 커널에서 커널 객체를 할당하는 데 사용되는 Slub 할당 자 및 메모리 페이지 할당에 사용되는 Buddy Allocator는 메모리 태깅을 지원합니다.
예를 들어 자세한 내용에 관심이있는 독자는이 기사와 ARM이 발표 한 백서를 참조하십시오.
소개에서 언급 했듯이이 악용은 MTE를 우회 할 수 있습니다. 그러나 GPU를 통해 해제 메모리 페이지에 액세스하는 이전의 이전 취약점과 달리이 버그는 사용자 공간 매핑을 통해 해제 메모리 페이지에 액세스합니다. 페이지 할당 및 DeCeferencing은 MTE에 의해 보호 되므로이 버그가 MTE를 우회하는 것이 다소 놀라운 일입니다. 처음에는 이것이 취약점에 관여하는 메모리 페이지가 kbase_mem_pool
Mali GPU 드라이버가 사용하는 사용자 정의 메모리 풀입니다. 악용에서 PGD가 관리되는 메모리 풀로 되돌아 가면서 재사용 된 무료 메모리 페이지가 kbase_mem_pool
그런 다음 메모리 풀에서 다시 할당되었습니다. 따라서 페이지는 친구 할당 자에 의해 진정으로 해방되지 않았으므로 MTE에 의해 보호되지 않았습니다. 이것이 사실이지만, 나는 또한 페이지를 올바르게 제거하고 버디 할당 자로 반환하기로 결정했습니다. 놀랍게도, MTE는 버디 할당자가 해제 된 후 페이지에 액세스 할 때에도 트리거되지 않았습니다. 일부 실험 및 소스 코드 읽기 후에는 Page Mappings가 mgm_vmf_insert_pfn_prot
~에 kbase_csf_user_io_pages_vm_fault
메모리 페이지가 해제 된 후에 액세스하는 데 사용되는 경우 궁극적으로 사용합니다. insert_pfn
매핑을 만들려면 페이지 프레임을 사용자 공간 페이지 테이블에 삽입합니다. 확실하지는 않지만 페이지 프레임이 사용자 공간 페이지 테이블에 직접 삽입되기 때문에 사용자 공간에서 해당 페이지에 액세스하려면 커널 레벨 디퍼링이 필요하지 않으므로 MTE를 트리거하지 않습니다.
결론
이 게시물에서는 커널 MTE가 활성화 된 픽셀 8에서 CVE-2025-0072를 사용하여 임의의 커널 코드 실행을 얻는 방법을 보여주었습니다. GPU에서 해방 된 메모리에 액세스하여 MTE를 우회 한 이전의 취약점과 달리이 취약점은 드라이버가 삽입 한 사용자 공간 메모리 매핑을 통해 해당 메모리에 액세스합니다. 이는 사용자 공간의 메모리 매핑을 통해 해제 메모리 페이지에 액세스 할 때 MTE를 우회 할 수 있음을 보여줍니다. 이는 이전 취약점보다 훨씬 일반적인 시나리오입니다.
작성자가 작성했습니다
Post Comment