🇰🇷 한글 설명
Linux 커널 x86/KVM에서, NMI 컨텍스트 중 커널 모드 #PF 발생 시 비동기 #PF 플래그(apf_reason.flags)를 잘못 소비하여 커널 패닉이 발생하는 문제가 수정되었습니다. NMI 처리 중 발생한 페이지 폴트가 인터럽트된 #PF의 플래그를 읽고 지워버리는 것이 원인이며, 패치는 커널 모드 NMI 컨텍스트에서 플래그를 건드리지 않도록 합니다.
💡 쉬운 설명
가상머신 환경에서 특정 타이밍에 시스템이 강제로 멈추는 버그입니다. 성능 분석 도구가 동시에 실행될 때 발생할 수 있습니다.
🇺🇸 원문 (English)
In the Linux kernel, the following vulnerability has been resolved:
x86/kvm: Don't consume async #PF flags on a kernel #PF in NMI context
An async #PF "page not present" event arrives through the #PF vector,
and its reason sits in the per-CPU apf_reason.flags until
__kvm_handle_async_pf() reads and clears it. If an NMI arrives after
the event is injected and before the #PF handler reads the flags, and
the NMI handler itself takes a #PF, that fault reads and clears the
flags belonging to the #PF it interrupted. It then finds interrupts
disabled in the NMI context it interrupted, and panics:
Kernel panic - not syncing: Host injected async #PF in interrupt
disabled region
Call Trace:
<NMI>
panic+0x52/0x60
__kvm_handle_async_pf+0xb0/0xb0
exc_page_fault+0x71/0xb0
asm_exc_page_fault+0x27/0x30
RIP: 0010:__get_user_nocheck_8+0x6/0x20
perf_callchain_user+0x147/0x1a0
get_perf_callchain+0x12d/0x1c0
...
perf_event_nmi_handler+0x26/0x50
exc_nmi+0xe8/0x110
RIP: 0010:asm_exc_page_fault+0x0/0x30
</NMI>
The last RIP line shows the NMI landing on the first instruction of
the #PF entry. perf with user call chains takes such a fault: the
sampling NMI walks the user stack with __get_user(), which faults when
a saved frame pointer is not a valid user address or the walk runs off
the top of the stack. The same window exists when KVM, running as an
L1 hypervisor with DELIVERY_AS_PF_VMEXIT, reads the flags itself after
a #PF VM-exit.
The NMI entry code saves and restores CR2 for this nesting, but not the
async #PF reason. Andy Lutomirski described the race, with perf as the
example, when async #PF delivery was reworked in 2020 [1]. Saving and
clearing the flags across the NMI, next to CR2, was discussed there but
not merged.
Since that rework, Linux has stopped setting KVM_ASYNC_PF_SEND_ALWAYS,
so the host delivers "page not present" only for user mode faults and
a kernel-mode fault in NMI context (including kernel-mode #MC and #DB)
cannot be an async page fault. In that case, return without touching
the flags so that the interrupted handler finds them intact.
Tested on v7.3-rc5 in a 4-vCPU, 2 GiB guest restored from a snapshot
whose memory is served on demand through userfaultfd, so that reading
it back takes async #PFs, while every thread samples hardware cycles
with user call chains and points its frame pointer at an unmapped page.
Without this patch the guest panicked as above in 3 of 3 runs. With it,
the guest survived 3 of 3 runs of 60 seconds, and the host counted
148,441 to 153,334 kvm_async_pf_not_present events in the first 20
seconds of each. On 6.18.50 the results were the same: the guest
panicked without the patch and survived with it.
📍 영향받는 시스템
KVM 기능을 사용하는 Linux 커널 (x86, 패치 이전 버전; 테스트 기준 v7.3-rc5 및 6.18.50)
⚔️ 공격 벡터
KVM 게스트 또는 호스트에서 NMI 기반 성능 샘플링과 비동기 페이지 폴트가 겹치는 조건을 유발하여 커널 패닉(서비스 거부)을 일으킬 수 있음
🛡️ 대응 방법
패치가 적용된 Linux 커널로 업데이트 (커널 배포판의 보안 업데이트 적용)
🔧 검증 코드
출력된 커널 버전이 패치가 포함된 배포판 업데이트 버전 이상인지 배포판 보안 공지(또는 changelog)에서 확인하며, 패치 포함 여부가 불명확하면 해당 커밋을 기준으로 판단합니다.