OpenBSD Big Fish Exploit: 37년 된 커널 버그로 로컬 루트 권한 획득
TL;DR
Calif의 연구원들은 OpenBSD의 SysV 공유 메모리 첨부 카운터에서 37년 된 정수 오버플로를 발견하고, 이를 경쟁 조건 및 범위를 벗어난 쓰기와 결합하여 KARL, SMAP, SMEP과 같은 현대적 완화 조치에도 불구하고 로컬 루트 셸을 획득했습니다. 이 버그는 OpenBSD 7.9 errata 026 및 018에서 수정되었으며, write-what-where 프리미티브는 커널 할당자 및 목록 연결 해제 코드를 강화할 수 있는 여러 방법을 제시합니다.
취약점
무슨 일이 있었나: sys/shm.h의 부호 있는 16비트 shm_nattch 카운터가 32,767개 이상의 공유 메모리 첨부가 생성될 때 오버플로되어 음수 값으로 감싸지고, 경쟁 조건을 통해 use-after-free를 가능하게 했습니다. 이 오버플로는 1987년경에 작성된 원래 BSD 소스 코드에서 비롯되었습니다.
왜 중요한가: 이 오버플로는 공격자가 커널 메모리 구조를 조작하여 SMAP/SMEP을 우회하고 커널 코드 실행을 허용하는 write-what-where 프리미티브를 가능하게 하며, 궁극적으로 매우 안전한 것으로 간주되는 시스템에서 루트 셸을 제공합니다.
적용된 수정: OpenBSD 7.9 errata 패치 026(sysvipc) 및 018(shmat)은 각각 카운터 오버플로와 범위를 벗어난 쓰기를 해결합니다.
익스플로잇 개요
핵심 아이디어: 익스플로잇은 공유 메모리 첨부 카운터를 음수 범위로 밀어 넣은 다음, 새 첨부에 대해 IPC_RMID를 경쟁시켜 참조를 유지하면서 커널 객체를 해제합니다. 범위를 벗어난 쓰기는 struct ip_moptions의 필드를 덮어쓰고, 이후 원자적 감소가 페이지 테이블 항목을 사용자 접근에서 슈퍼바이저 접근으로 전환하여 임의의 커널 쓰기를 가능하게 합니다.
단계별 요약:
- 카운터 감싸기: 127개의 SysV 공유 메모리 세그먼트를 매핑하고, 매핑을 해제한 다음, 259개의 프로세스를 포크하여
shm_nattch를 -32,643으로 밀어 넣습니다. - 힙 그루밍:
sysctl(KERN_MALLOCSTATS)를 사용하여 커널 malloc 통계를 모니터링하고 할당 시간을 조정합니다. - 경쟁 조건: 한 프로세스에서
IPC_RMID를 호출하는 동안 헬퍼 프로세스가 크기가 혼동된 세그먼트를 첨부하여 use-after-free를 생성합니다. - 소켓 구조 준비: IP 소켓을 통해 영구적인
struct ip_moptions를 할당하고IP_MULTICAST_TTL=2를 설정하여 구조에 제어 가능한 카운터를 배치합니다. - 범위를 벗어난 쓰기: 조건부
-1쓰기를 악용하여imo_num_memberships및imo_max_memberships필드를 손상시킵니다. - 원자적 감소 트리거: 소켓을 닫으면 멤버십 목록을 순회하여 두 번의 원자적 감소가 발생하고, 자기 참조 PTE 매핑을 다시 작성하여 사용자 제어 페이지를 슈퍼바이저 접근 가능하게 만듭니다.
- Write-what-where: 예측 가능한 커널 포인터(예:
ioctl핸들러)를 공격자가 제어하는 셸코드에 대한 포인터로 덮어쓴 다음, 루트 권한을 얻은 후 포인터를 복원합니다.
결과: KARL, SMAP, SMEP이 활성화된 OpenBSD 7.9에서 정보 공개 버그 없이 안정적인 로컬 루트 셸을 얻습니다.
완화 조치 현황
즉시 패치
- Errata 026 – 공유 메모리 첨부 카운터의 부호 있는 short 오버플로를 수정합니다.
- Errata 018 –
shmat처리에서 범위를 벗어난 쓰기를 패치합니다.
구조적 강화 제안
| 제안 | 보호 대상 | 구현 힌트 |
|---|---|---|
| 안전한 연결 해제 | 손상된 이중 연결 목록(TAILQ_REMOVE)을 통한 use-after-free 방지 |
연결 해제 전에 불변 조건 검사 추가; 위반 시 중단/패닉. FreeBSD/NetBSD는 이미 sys/queue.h에 이를 포함합니다. |
| 자기 참조 PTE 맵 무작위화 | 페이지 테이블 베이스에 대한 결정적 원자적 감소 공격 방지 | 부팅 시 임의의 PML4 슬롯 선택(320 + (rand & 127)) 및 재귀 항목을 해당 위치에 설치 |
| 비결정적 커널 할당자 | 포인터 덮어쓰기에 사용되는 예측 가능한 할당 주소 차단 | 부팅 시 할당자 베이스 주소 무작위화 및 런타임 시 할당 순서 무작위화(CalANDif 커밋 참조) |
| 컴파일러 수준 샌티타이저 | 악용 가능해지기 전에 범위를 벗어난 배열 쓰기 및 use-after-free 포착 | 릴리스 빌드에서 Clang의 Array Bounds Sanitizer(UBSan) 활성화; 향후 AllocTokens는 포인터 포함 할당을 격리할 수 있음 |
OpenBSD의 기존 완화 조치가 충분하지 않은 이유
- KARL은 함수 및 데이터 주소를 무작위화하지만 할당자 베이스 주소는 무작위화하지 않아 초기 부팅 구조가 예측 가능한 위치에 남습니다.
- SMAP/SMEP은 사용자 모드의 슈퍼바이저 메모리 접근을 차단하지만, 익스플로잇은 PTE 항목을 슈퍼바이저 접근 가능으로 전환하여 이러한 보호를 우회합니다.
커뮤니티 통찰
"가장 철저히 감사된 코드베이스 중 하나에서 버그가 37년 동안 살아남은 것은 놀라운 일이며, 이는 일부 커널 버그가 검토만으로는 얼마나 잡기 어려운지 보여줍니다. 퍼징이나 정적 분석이 이 클래스의 버그를 현실적으로 찾을 수 있었을까요, 아니면 익스플로잇 프리미티브에 대해 생각하는 인간이 필요했을까요?" – @ozereray1의 HN 댓글
토론은 광범위한 수동 검토조차도 특히 특정 익스플로잇 프리미티브가 필요한 경우 미묘한 정수 오버플로 버그의 부재를 보장할 수 없음을 강조합니다. 다양한 포크 패턴으로 많은 수의 shmat/shmdt 시퀀스를 실행하는 자동화된 퍼징은 이론적으로 오버플로를 표면화할 수 있지만, 경쟁 조건 및 범위를 벗어난 쓰기는 대상화된 기호 실행이나 수동 추론이 필요할 것입니다.
커널 보안 엔지니어를 위한 교훈
- 레거시 데이터 구조 감사 – 부호 있는 short 카운터 및 기타 좁은 정수 필드는 레거시 코드가 이식될 때 오버플로의 주요 후보입니다.
- 결정적 할당자는 악용 가능하다고 가정 – 예측 가능한 할당 주소는 write-what-where 프리미티브를 가능하게 합니다; 부팅 및 런타임 무작위화는 저비용 완화 조치입니다.
- 계층적 방어가 중요 – 주소 공간 무작위화, KASLR, SMAP/SMEP이 있어도 공격자는 페이지 테이블 계층 자체를 손상시켜 코드 실행을 얻을 수 있습니다.
- 기존 안전한 연결 해제 구현 활용 – FreeBSD와 NetBSD는 이미 손상된 이중 연결 목록 제거로부터 보호합니다; OpenBSD는 최소한의 영향으로 동일한 기술을 채택할 수 있습니다.
- 컴파일러 샌티타이저 조기 채택 – UBSan의 배열 경계 검사를 활성화하고 AllocTokens를 준비하면 프로덕션 커널에 도달하기 전에 많은 범위를 벗어난 쓰기를 잡을 수 있습니다.
참고 자료
- 원본 연구 블로그: https://calif.io/research/big-fish
- OpenBSD 7.9 errata 패치: 026_sysvipc.patch, 018_shmat.patch
- 익스플로잇 PoC 소스: https://github.com/califio/publications/tree/main/MADBugs/openbsd/big-fish.c
- 안전한 연결 해제 구현 예: https://github.com/CalANDif/openbsd-src/commit/dc16d6afe2d897c5e2be8227f737c0d2ebdbfc86
- 무작위화된 PTE 맵 구현: https://github.com/CalANDif/openbsd-src/commit/89db398d53e6698a80eaf72f32d1a3e544d2e95f
- 할당자 무작위화 커밋: https://github.com/CalANDif/openbsd-src/commit/94c29e3432ad016ce34004d52594382a166ee707 및 https://github.com/CalANDif/openbsd-src/commit/dd6cd7b9655b1a7fdfabfa97f82ead646e15333a
- 배열 경계 샌티타이저 논의: https://github.com/freebsd/freebsd-src/pull/2404