StackRot
StackRot
cve-2023-3269 리눅스 커널 권한 상승 취약점
리눅스 커널 버전 6.1 이전 VMA(가상 메모리 영역) red-black tree
이후 maple tree
[요약]
커널의 메모리 관리 red-black tree -> maple tree
이때 MM 쓰기 lock 없이 노드 교체될 수 있음
use-after-free 으로 커널 손상 & 권한 상승
[*]
할당된 주소 공간, 페이지, 메모리 정보 mm_struct 관리
연속적인 주소 공간을 나타내는 VMA
VMA(가상 메모리 영역) 노드를 관리하는 자료 구조 -> maple tree
~~2학년 1학기 자료구조 시간에서 배웁니다~~
Root Node
└── Maple Node
├── Interval 1: [start_address1, end_address1]
├── Interval 2: [start_address2, end_address2]
├── ...
├── Interval 15: [start_address15, end_address15]
└── Interval 16: [start_address16, end_address16]
각 노드는 interval(범위)와 physical address 포함
stack은 프로세스가 동작하며 메모리 공간이 동적으로 할당/해제됨
위 가상 메모리 영역 또한 할당/해제됨 -> 노드 교체 발생
노드 교체가 발생하면서 lock 사용 -> 동시성 문제 해결
~~자세한 건 2학년 2학기 운영체제 시간에서 배웁니다~~
[+]
1. mmap() 호출하면...
2. VMA(vm_area_struct) 구조 생성
struct vm_area_struct {
long unsigned int vm_start; /* 0 8 */
long unsigned int vm_end; /* 8 8 */
struct mm_struct * vm_mm; /* 16 8 */
pgprot_t vm_page_prot; /* 24 8 */
long unsigned int vm_flags; /* 32 8 */
union {
struct {
struct rb_node rb __attribute__((__aligned__(8))); /* 40 24 */
/* --- cacheline 1 boundary (64 bytes) --- */
long unsigned int rb_subtree_last; /* 64 8 */
} __attribute__((__aligned__(8))) shared __attribute__((__aligned__(8))); /* 40 32 */
struct anon_vma_name * anon_name; /* 40 8 */
} __attribute__((__aligned__(8))); /* 40 32 */
/* --- cacheline 1 boundary (64 bytes) was 8 bytes ago --- */
struct list_head anon_vma_chain; /* 72 16 */
struct anon_vma * anon_vma; /* 88 8 */
const struct vm_operations_struct * vm_ops; /* 96 8 */
long unsigned int vm_pgoff; /* 104 8 */
struct file * vm_file; /* 112 8 */
void * vm_private_data; /* 120 8 */
/* --- cacheline 2 boundary (128 bytes) --- */
atomic_long_t swap_readahead_info; /* 128 8 */
struct vm_userfaultfd_ctx vm_userfaultfd_ctx; /* 136 0 */
/* size: 136, cachelines: 3, members: 14 */
/* forced alignments: 1 */
/* last cacheline: 8 bytes */
} __attribute__((__aligned__(8)));
3. MAP_GROWSDOWN 옵션으로 mmap 호출하면 스택 확장
아래 코드에서 expand_stack(vma, address) 참고, 아래 코드에서 write lock 획득을 하지 않아도 수행됨
static inline
void do_user_addr_fault(struct pt_regs *regs,
unsigned long error_code,
unsigned long address)
{
// ...
if (unlikely(!mmap_read_trylock(mm))) {
// ...
}
// ...
if (unlikely(expand_stack(vma, address))) {
// ...
}
// ...
}
3. 스택 확장 프로세스
mm_read_lock을 사용하면 maple tree의 안전은 지킬 수 있지만 VMA 자체가 확장되는 변경은 보호 X
스택: memory area mapped with the MAP_GROWSDOWN flag
이 영역 아래의 주소에 접근할 때 확장->해당 VMA의 시작 주소와 maple 트리 gap 조정
이 조정은 MM 쓰기 잠금 없이 이루어짐
int expand_downwards(struct vm_area_struct *vma, unsigned long address)
{
// ...
if (prev) {
if (!(prev->vm_flags & VM_GROWSDOWN) &&
vma_is_accessible(prev) &&
(address - prev->vm_end < stack_guard_gap))
return -ENOMEM;
}
// ...
}
커널에서 시행되는 스택 가드: VMA 사이 gap 존재
인접 VMA도 MAP_GROWSDOWN 플래그면, 스택 가드 적용 X
결국 stack 확장 -> gap 제거
간격 제거 -> 노드를 업데이트 해야함
새 노드 생성 -> 교체 -> 이전 노드는 RCU 콜백을 사용하여 삭제
static inline void mas_wr_modify(struct ma_wr_state *wr_mas)
{
// ...
if ((wr_mas->offset_end - mas->offset <= 1) &&
mas_wr_slot_store(wr_mas)) // <-- in-place update
return;
else if (mas_wr_node_store(wr_mas)) // <-- node replacement
return;
// ...
}
[*]
RCU(Read-Copy-Update) 동기화 매커니즘
동기화는 운영체제에서 중요한 문제(같은 자원인데 스레드마다 다른 데이터가 입력되면 안되니까)
1. 공유 자원에 대해 읽기-쓰기 동시 발생 -> 동기화
2. 읽기는 락을 걸지 않고 수행
3. 쓰기는 데이터 갱신 -> 이전 데이터 정리
maple tree는 RCU-safe(동기화로 인한 데이터 일관성 보장)
[*]
VMA 접근 방법 두 가지
option 1) lock 걸고
option 2) 임계 영역(critical section) 진입
4. RCU 콜백
RCU 임계 영역 해제 완료 후 RCU 콜백 호출 가능
but VMA 접근 시, MM-read-lock 유지하는 첫번째 옵션을 이용하면 두번째 옵션인 critical section에 들어가지 않기 때문에 RCU 콜백이 언제든 호출될 수 있음
-> 이러면 언제든 이전 메이플 노드 해제 가능
이전 노드는 콜백에 의해 free+근데 이전 노드에 대한 포인터를 가져오면?=UAF 취약점
-----아래부터는 방학에 추가~...-----
[익스플로잇]
2개의 CPU
slab 당 maple 노드 수 16로 가정
다음 기준 충족하는 VMA iteration
1. iteration 제어 가능해야함
2. iteration이 VMA에서 정보 검색하고 반환할 수 있어야 함
3. iteration이 특정 함수 포인터 호출 가능해야 함
/proc/[pid]/maps contents 생성 담당 VMA iteration
1) 이전 노드 해제되는 타이밍 결정: synchronize_rcu() 사용 -> RCU 유예 기간이 종료될 때까지 대기
SYSCALL_DEFINE3(membarrier, int, cmd, unsigned int, flags, int, cpu_id)
{
// ...
switch (cmd) {
// ...
case MEMBARRIER_CMD_GLOBAL:
/* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
if (tick_nohz_full_enabled())
return -EINVAL;
if (num_online_cpus() > 1)
synchronize_rcu();
return 0;
// ...
}
}
2) 이전 노드가 해제되기 전에 VMA 반복 X
고른 iteration은 파일이 매핑된 메모리 영역의 전체 경로 생성
디렉토리가 아주 깊은 파일을 만들고 파일을 매핑하면 iteration은 상당한 시간 소요
그 사이 RCU 유예 기간 종료하고 UAF primitive 획득
static void
show_map_vma(struct seq_file *m, struct vm_area_struct *vma)
{
// ...
/*
* Print the dentry name for named mappings, and a
* special [heap] marker for the heap:
*/
if (file) {
seq_pad(m, ' ');
/*
* If user named this anon shared memory via
* prctl(PR_SET_VMA ..., use the provided name.
*/
if (anon_name)
seq_printf(m, "[anon_shmem:%s]", anon_name->name);
else
seq_file_path(m, file, "\n");
goto done;
}
// ...
}