CVE-2026-23086浅析
背景
在全虚拟化中,宿主机需要通过软件「模拟」真实的物理硬件。当虚拟机执行特权指令时(例如,读写硬件的寄存器),Hypervisor便需要中断处理。这需要特权切换,因此性能开销较大。而通过半虚拟化技术,虚拟机能够向虚拟机管理程序表明其意图,从而提升性能。
VirtIO定义了设备驱动和硬件(包括虚拟硬件和物理硬件)之间的通信标准,主要用于半虚拟化环境。其允许虚拟机通过VirtIO设备来使用宿主机的物理设备,VirtIO设备本身主要负责数据传输。VirtIO架构主要分为三个部分:前端驱动程序、后端设备、VirtQueue及VRing。三者的关系如下图所示(参考了该图片):
flowchart TD
subgraph guest_env [客户机执行环境]
subgraph guest_user [客户机—用户空间]
GuestApp[客户机应用程序]
end
subgraph guest_kernel [客户机—内核]
subgraph FrontDrivers [前端驱动程序]
VNetDriver["virtio-net驱动"]:::driver
VScsiDriver["virtio-scsi驱动"]:::driver
end
end
GuestApp -.->|系统调用/IO请求| FrontDrivers
end
subgraph host_user [宿主机—用户空间]
subgraph Qemu [Qemu进程]
subgraph backend_virtio [后端设备]
VNetDevice["virtio-net设备"]:::device
VScsiDevice["virtio-scsi设备"]:::device
end
end
end
subgraph host_kernel [宿主机—内核]
KVM(["KVM"]):::kvm
end
VNetDriver <==>|VirtQueues及VRings| VNetDevice
VScsiDriver <==>|VirtQueues及VRings| VScsiDevice
VNetDriver -.->|VM-Exit陷入| KVM
VScsiDriver -.->|VM-Exit陷入| KVM
VNetDevice -.->|发起中断注入| KVM
VScsiDevice -.->|发起中断注入| KVM
virtio-vsock是VirtIO家族中用于socket通信的设备类型,定义了AF_VSOCK地址族,用于宿主机与虚拟机之间的通信。virtio-vsock在传输层之上引入了一套credit流控机制,核心流程如下:
1 | 用户send(fd, buf, N) |
攻击流程
get_credit的返回值ret是min(用户想发的字节数, 对端剩余缓冲区空间),而剩余空间由virtio_transport_has_space计算:
1 | bytes = (s64)vvs->peer_buf_alloc - (vvs->tx_cnt - vvs->peer_fwd_cnt); |
这里tx_cnt是本端累计已发字节数(每次send()累加),peer_fwd_cnt是对端累计已消费字节数,peer_buf_alloc是对端通告的缓冲区大小。
攻击的关键在于peer_buf_alloc由对端控制,本地并无校验或额外限制。攻击者可先将缓冲区大小上限SO_VM_SOCKETS_BUFFER_MAX_SIZE提高到足够大的值,然后将缓冲区大小SO_VM_SOCKETS_BUFFER_SIZE设为较大的值(如2GiB),该值将通过数据包携带给对端,更新其peer_buf_alloc。攻击者随后向受害者发起连接并发送请求,受害者服务响应该请求时,会调用send()回传数据。此时send_pkt_info内部的do-while循环将pkt_len(即实际发送的字节数)全部切分为sk_buff并放入VirtQueue。
在此基础上,攻击者可以缓慢调用recv(),导致受害者的peer_fwd_cnt增长极慢,而tx_cnt随着每次回传数据持续累积。由于peer_buf_alloc被谎报为较大的值,并且send()要等到未确认数据到达对应阈值才会阻塞;因此若对端需要发送大量数据(如文件传输等场景),则单条连接可能存在接近SO_VM_SOCKETS_BUFFER_SIZE值的sk_buff滞留内存。攻击者同时维持多条此类连接,即可耗尽系统的内存资源。
复现
根据上述原理,我编写了PoC,复现了相关效果。其中,受害方运行一个Python脚本,监听相应端口(示例中为1122),若有请求则会不断发送数据;攻击方运行一个C语言程序,其向受害方请求连接,伪造了peer_buf_alloc,并缓慢接收数据。
1 | import socket |
下列程序中,TARGET_CID为VSOCK的ID,若使用Incus,可通过incus config get <vm_name> vsock.id获取。
1 |
|
我将宿主机作为攻击方,虚拟机作为受害方。虚拟机中运行Ubuntu 22.04,内核版本为5.15.0-185-generic。根据官方公告,该内核版本受影响。
运行过程中使用watch -n 0.5 'grep -E "Slab|MemAvailable" /proc/meminfo'监视虚拟机的内存占用情况。可以发现,虚拟机中内存使用量持续上升,并且无法继续建立VSOCK连接,最终导致Incus的shell退出。

修复与后续
Linux内核于该commit中修复了该问题。其引入了virtio_transport_tx_buf_size函数,将额度限制为peer_buf_alloc和buf_alloc的较小值,而buf_alloc为本端的缓冲区大小上限。