[하루한줄] CVE-2025-39965: Linux Kernel XFRM subsystem의 UAF 취약점
URL
https://streypaws.github.io/posts/Dissecting-a-1-Day-Vulnerability-in-Linux-XFRM-Subsystem/
Target
- Ubuntu: https://ubuntu.com/security/CVE-2025-39965
- Red Hat: https://access.redhat.com/security/cve/cve-2025-39965
- SUSE: https://www.suse.com/security/cve/CVE-2025-39965.html
- Debian: https://security-tracker.debian.org/tracker/CVE-2025-39965
Explain
리눅스 커널의 IPsec 프로토콜을 구현하는 XFRM 하위 시스템에서 발견된 Use-After-Free 취약점 CVE-2025-39965에 대한 세부 정보가 공개되었습니다.
취약점은 최근 94f39804d891cffe4ce17737d295f3b195bc7299 커밋에서 변경된 net/xfrm/xfrm_state.c 파일의 xfrm_alloc_spi 함수에 존재합니다. 해당 함수는 사용자 공간으로부터 Netlink 메시지인 XFRM_MSG_ALLOCSPI를 통해 호출되며, SPI를 할당하는 역할을 합니다.
int xfrm_alloc_spi(struct xfrm_state *x, u32 low, u32 high,
struct netlink_ext_ack *extack)
{
//...
for (h = 0; h < range; h++) {
// Return value of get_random_u32_inclusive can be 0.
u32 spi = (low == high)? low : get_random_u32_inclusive(low, high);
newspi = htonl(spi);
spin_lock_bh(&net->xfrm.xfrm_state_lock);
x0 = xfrm_state_lookup_spi_proto(net, newspi, x->id.proto);
if (!x0) {
x->id.spi = newspi;
h = xfrm_spi_hash(net, &x->id.daddr, newspi,
x->id.proto, x->props.family);
// New xfrm_state obj with spi=0
hlist_add_head_rcu(&x->byspi, net->xfrm.state_byspi + h);
err = 0;
spin_unlock_bh(&net->xfrm.xfrm_state_lock);
goto unlock;
}
//...
}
해당 커밋은 SPI 중복 방지를 위해 할당 로직을 변경하였는데, 그 과정에서 get_random_u32_inclusive 함수가 0을 반환할 수 있지만 이를 검사하지 않습니다. 이로 인해 xfrm_state 객체가 spi 값이 0인 경우에도 경우 유효한 값으로 할당되어 byspi 전역 목록에 추가됩니다.
int __xfrm_state_delete(struct xfrm_state *x)
{
//...
//If spi is 0, it's not removed from byspi list.
if (x->id.spi) {
hlist_del_rcu(&x->byspi);
}
xfrm_state_free(x);
return 0;
}
그러나 XFRM 하위 시스템에서 SPI=0은 일반적으로 할당된 SPI 없음을 의미하기 때문에, 해당 객체를 정리하는 __xfrm_state_delete 함수는 spi 값이 0이면 byspi 목록에서 제거하지 않습니다.
따라서 메모리는 해제되지만, byspi 목록에는 해당 메모리를 가리키는 dangling pointer가 남아 UAF가 트리거됩니다.
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
int xfrm_alloc_spi(struct xfrm_state *x, u32 low, u32 high,
struct netlink_ext_ack *extack)
{
//...
for (h = 0; h < range; h++) {
u32 spi = (low == high)? low : get_random_u32_inclusive(low, high);
+ if (spi == 0)
+ goto next;
newspi = htonl(spi);
//...
xfrm_state_put(x0);
spin_unlock_bh(&net->xfrm.xfrm_state_lock);
+next:
if (signal_pending(current)) {
err = -ERESTARTSYS;
goto unlock;
취약점의 패치 커밋(cd8ae32e4e4652db55bce6b9c79267d8946765a9)은 xfrm_alloc_spi 함수가 spi 객체를 할당하기 전, spi값이 0이면 할당을 하지 않도록 이루어졌습니다.
본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.