뉴스 스터디

StackRot

육키티 2023. 8. 9. 12:42

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;
	}

	// ...
}