[하루한줄] CVE-2025-39965: Linux Kernel XFRM subsystem의 UAF 취약점

URL

https://streypaws.github.io/posts/Dissecting-a-1-Day-Vulnerability-in-Linux-XFRM-Subsystem/

Target

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 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.