본문 바로가기
CVE 취약점

CVE-2026-53365 분석: "마지막 조각에만" 완료 추적을 붙였던 커널 vsock 제로카피의 함정

by WhiteGuidance 2026. 10. 18.
반응형

개요

항목 내용
CVE ID CVE-2026-53365
영향 제품 Linux 커널 vsock/virtio (virtio-vsock) 서브시스템
영향 버전 6.7 ~ 7.0.10 (해당 안정 브랜치 포함)
패치 버전 7.0.11, 6.12.97 및 각 배포판의 백포트
CVSS Red Hat 평가 기준 CVSS 3.1 AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H — NVD/일부 스캐너는 메모리 누수 관점에서 5.5(Medium)로도 집계
취약점 유형 CWE-401 (유효 수명 이후 메모리 미해제), CWE-404 (자원의 부적절한 해제) — 공개 PoC는 이를 Use-After-Free·레퍼런스 카운트 언더플로로 확장해 로컬 권한 상승까지 시연
전제조건 로컬에서 코드를 실행할 수 있어야 하며, io_uring이 활성화되어 있고 /dev/vsock에 접근 가능해야 함 (기본 권한이 0666인 경우가 많아 일반 사용자도 접근 가능한 경우가 흔함)

 

이번 CVE는 공격면이 HTTP 요청이 아니라 커널의 소켓 버퍼(skb) 레퍼런스 카운팅이고, 피해자도 서버가 아니라 같은 머신 위의 커널 그 자체입니다. AF_VSOCK은 가상머신과 호스트, 혹은 같은 머신 안의 프로세스들이 TCP/IP 스택을 거치지 않고 통신할 수 있게 해주는 소켓 패밀리이고, MSG_ZEROCOPY는 이 통신에서 데이터를 커널 버퍼로 복사하지 않고 유저 공간 페이지를 그대로 "핀(pin)"해서 전송 성능을 높이는 기능입니다.

 

핀을 걸었다는 건 그 페이지가 전송이 끝날 때까지 다른 용도로 재사용되지 않는다는 보장입니다. 이 보장이 깨지면, 유저 공간은 여전히 그 페이지에 쓰기를 할 수 있다고 믿는데 커널은 같은 물리 페이지를 이미 다른 용도로 내준 상태가 되는, 전형적인 Use-After-Free류 메모리 손상으로 이어질 조건이 갖춰집니다. 이번 CVE는 큰 메시지가 여러 개의 skb로 쪼개질 때, 그 핀 해제 추적이 딱 하나의 skb에 대해서만 제대로 동작했다는 데서 시작됩니다.


취약점이 숨어있던 자리 - 큰 메시지를 skb 여러 개로 쪼개 보내는 송신 루프

virtio-vsock은 virtio_transport_send_pkt_info() 함수에서 보낼 메시지를 가상 큐(virtqueue)에 맞는 크기의 skb들로 나눠 순차적으로 채워 넣습니다. MSG_ZEROCOPY를 쓰는 전송이라면, 이 과정에서 유저 페이지를 핀 고정한 ubuf_info(흔히 uarg로 줄여 부릅니다)라는 구조체를 skb에 매달아 둬야 합니다. 이 uarg가 바로 "이 skb가 다 전송되고 나면, 핀 고정했던 페이지를 유저 공간에 돌려줘도 된다"는 완료 통지를 담당합니다.


단계별 소스코드 분석

1단계 — 정상 설계: 핀 고정과 완료 통지는 1:1로 짝지어져야 한다

MSG_ZEROCOPY의 기본 계약은 단순합니다. 페이지 하나를 핀 고정해 보냈다면, 그 페이지를 담은 skb가 실제로 전송(또는 폐기)될 때 정확히 한 번 완료 콜백이 불려야 하고, 그 콜백 안에서 핀을 해제해야 합니다. TCP 같은 기존 전송 계층은 이 계약을 skb 하나하나에 전부 uarg를 연결하는 방식으로 지킵니다.

 

2단계 — 결함: uarg가 "마지막 skb"에만 연결되었다 (패치 전)

virtio-vsock의 송신 루프는 메시지를 여러 skb로 쪼갤 때, 패치 전 코드 기준으로 다음과 같은 조건 하에서만 uarg를 만들어 skb에 연결했습니다(커널 공식 패치 메일에 공개된 diff를 바탕으로 재구성).

 

virtio_transport_init_zcopy_skb() 내부에서는 msg_zerocopy_realloc()으로 uarg를 할당하고 skb_zcopy_init(skb, uarg)로 연결하는데, 이 호출 전체가 skb_len == rest_len(즉 "이 skb를 채우고 나면 남은 길이가 0이 되는, 메시지의 진짜 마지막 조각") 조건 안에 있습니다. 메시지가 skb 하나로 끝나지 않고 여러 조각으로 쪼개지는 경우, 마지막 skb를 제외한 나머지 skb들은 이 블록을 아예 건너뜁니다. 그 skb들도 똑같이 유저 페이지를 핀 고정한 채 전송 대기열에 들어가지만, 그 핀에 대응하는 완료 추적(uarg)은 어디에도 연결되어 있지 않습니다.

 

3단계 — 핵심 결함: 완료 콜백 없는 skb가 반납될 때, 핀 카운트가 "원래보다 한 번 더" 줄어든다
여기서 중요한 건 "중간 skb에 uarg가 없다"는 사실 자체가 아니라, 그 skb가 정상적으로 free되는 과정에서 벌어지는 일입니다. uarg 없는 skb라도 전송이 끝나면 커널은 그 skb를 평범하게 해제하면서, 거기 담겨 있던 페이지에 대해 put_page()를 호출합니다. 정상적인 경로(TCP 등)라면 "핀 고정 시 늘렸던 레퍼런스를 완료 콜백이 정확히 한 번 되돌려준다"는 짝이 맞지만, 이 결함에서는 그 짝이 되어야 할 완료 콜백 자체가 없는 상태에서 put_page()만 호출됩니다.

정상 경로 (uarg 있음): 핀 고정(+1) → skb 전송 완료 → zerocopy 완료 콜백(-1) → 레퍼런스 0, 안전하게 반환
결함 경로 (uarg 없음): 핀 고정(+1) → skb가 그냥 free됨 → put_page()만 호출 → 짝 없는 추가 감소

 

MSG_ZEROCOPY 전송을 반복할 때마다, 중간 skb 하나당 "원래 계산에 없던 감소"가 한 번씩 더 쌓입니다. 즉 같은 유저 버퍼를 대상으로 멀티 skb 전송을 충분히 반복하면, 그 페이지에 대한 커널의 핀 카운트(바이어스)가 실제로 유저 공간이 기대하는 값보다 점점 더 빨리 0에 도달합니다.

 

4단계 — 핀 카운트가 0이 되는 순간, 그 물리 페이지는 "자유" 상태로 보인다

핀 카운트가 0이 되면 커널 입장에서는 그 페이지를 더 이상 아무도 사용하지 않는다고 판단해 일반적인 페이지 회수 경로(퍼-CPU 페이지 프리리스트 등)로 돌려보낼 수 있습니다. 하지만 실제로는 유저 공간(공격자)이 그 페이지를 가리키는 별칭을 여전히 들고 있고, 그 페이지에 쓰기를 할 수 있는 상태입니다. 바로 이 지점부터가 전형적인 Use-After-Free류 문제로 넘어가는 경계선입니다 

 

같은 물리 페이지가 "공격자가 쓸 수 있는 버퍼"인 동시에 "커널이 완전히 자유라고 믿고 재할당한 자원"이라는 두 가지 역할을 동시에 갖게 됩니다.

 

 


패치 - TCP가 이미 쓰던 방식으로: 루프 시작 전 1회 할당, 모든 skb에 레퍼런스 분배

커널 패치는 virtio_transport_send_pkt_info()의 구조 자체를 바꿨습니다. uarg를 "마지막 skb를 채울 때" 할당하는 대신, 송신 루프에 들어가기 전에 미리 한 번만 할당하고, 루프 안에서는 매 skb마다 skb_zcopy_set(skb, uarg, NULL)을 호출해 레퍼런스를 하나씩 더합니다.

 

이렇게 하면 각 skb의 완료 처리가 uarg의 레퍼런스 카운트를 정확히 하나씩 깎고, 전체 레퍼런스가 0이 되는 시점(= 모든 skb가 실제로 전송 완료된 시점)에만 유저 공간에 완료 통지가 갑니다. 중간에 하나라도 데이터를 보내지 못하고 루프를 빠져나가는 경우에도, net_zcopy_put_abort()로 할당해 둔 uarg를 깔끔하게 정리해 레퍼런스가 어디에도 유실되지 않도록 합니다.

 

이 사례가 주는 원칙은 명확합니다. "하나의 논리적 전송"이 여러 개의 하위 단위(skb)로 쪼개질 수 있는 코드에서는, 자원 수명 관리(여기서는 핀 고정 해제)를 그 논리적 전송 단위 전체를 기준으로 설계해야 합니다. "마지막 조각에서 한 번만 처리하면 되겠지"라는 가정은, 바로 그 마지막 조각이 아닌 나머지 조각들이 "똑같이 자원을 쥐고 있다"는 사실을 놓치기 쉽습니다.


대응 방안

  • 커널을 7.0.11, 6.12.97 또는 사용 중인 배포판에서 이 CVE에 대해 발표한 백포트 버전으로 업데이트합니다.
  • 영향을 받는 코드 경로는 virtio-vsock(또는 vhost-vsock)으로 MSG_ZEROCOPY를 실제로 사용하는 환경에서만 도달 가능합니다. AF_VSOCK을 쓰지 않는 시스템이라면 이 결함의 영향을 받지 않습니다.
  • 업데이트가 즉시 어려운 다중 사용자 시스템이라면, /dev/vsock의 접근 권한을 필요한 사용자/그룹으로 제한하는 것이 임시 완화책이 될 수 있습니다. 기본값이 0666인 배포판이 적지 않다는 점을 기억해야 합니다.
  • 컨테이너나 VM 환경에서 io_uring을 일반 사용자 네임스페이스에까지 노출하고 있다면, 이런 종류의 로컬 권한 상승 취약점의 공격면이 넓어진다는 점을 함께 점검합니다.
반응형