Skip to contentSkip to conversation

vhost log mapping double offset

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

lore
Share & tools
lore
#0 lore raw

https://bugs.dpdk.org/show_bug.cgi?id=2004

Bug ID: 2004
Summary: vhost log mapping double offset
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-29
Reported by: Don Salvatore Nero <[email protected]>

Dear Thomas, Maxime, and the DPDK Security Team,

1. Executive Summary

An out-of-bounds write vulnerability has been identified in the DPDK vhost library, specifically within the vhost_user_set_log_base() function in lib/vhost/vhost_user.c. The vulnerability arises from a double-application of the guest-controlled mmap_offset value: once as a file offset in the mmap() system call, and a second time as an arithmetic addition to the resulting virtual address when computing log_base. This causes log_base to point beyond the boundaries of the mapped memory region, enabling a malicious guest virtual machine to trigger out-of-bounds memory writes on the host.

This report also demonstrates that the fix applied for CVE-2020-10722 was incomplete. The root cause of the offset double-application pattern was not fully remediated, and the vulnerable logic remains present in the stable 25.11.0 release.

2. Affected Component

FieldDetailsFilelib/vhost/vhost_user.cFunctionvhost_user_set_log_base()Primary Lines2401, 2416–2418, 2419Secondary Filelib/vhost/vhost.cSecondary Functions__vhost_log_write(), __vhost_log_cache_sync()Secondary Lines141–159, 197–204DPDK Version25.11.0 Stable

3. Background and Relationship to CVE-2020-10722

CVE-2020-10722 was a buffer overflow vulnerability in DPDK's vhost library related to improper handling of guest-supplied values. The official fix addressed specific integer overflow and boundary check issues. However, the fix was incomplete:

It prevented arithmetic overflow of off + size via the guard at line 2386.

It did not correct the fundamental logical error of applying mmap_offset twice — once as an mmap() file offset and again as pointer arithmetic on the returned address.

It did not align vhost_user_set_log_base() with the correct pattern already established in vhost_user_mmap_region().

The memory corruption primitive described in this report therefore remains present and reachable in DPDK 25.11.0.

4. Vulnerability Description

4.1 Root Cause

The function vhost_user_set_log_base() processes a VHOST_USER_SET_LOG_BASE message from a guest VM. The message payload contains two attacker-controlled values:

mmap_size — size of the log memory region.

mmap_offset — offset into the shared log file.

The function maps the log file into host memory using mmap(), passing mmap_offset as the file offset. It then adds mmap_offset a second time to the returned virtual address when storing log_base. This positions log_base exactly off bytes past the start of the mapped region, while the mapped region only covers size bytes starting at addr. Any indexed write beyond size - off bytes from log_base is therefore an out-of-bounds write.

4.2 Vulnerability Path — Step by Step

Step 1 — Guest sends VHOST_USER_SET_LOG_BASE (lines 2382–2383):

size = ctx->msg.payload.log.mmap_size; // attacker-controlled off = ctx->msg.payload.log.mmap_offset; // attacker-controlled

Step 2 — mmap() with off as file offset (line 2401):

addr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, off);

This maps exactly size bytes from file position off. Valid host virtual address range: [addr, addr + size).

Step 3 — off added a second time to compute log_base (lines 2416–2418):

dev->log_addr = (uint64_t)(uintptr_t)addr; dev->log_base = dev->log_addr + off; // BUG: addr + off exceeds the mapped region dev->log_size = size;

log_base now points to addr + off. The mapped region ends at addr + size. If off > 0, only max(0, size - off) bytes of valid mapped memory exist ahead of log_base.

Step 4 — Out-of-bounds write during dirty page logging (lib/vhost/vhost.c, lines 141–159):

void __vhost_log_write(struct virtio_net *dev, uint64_t addr, uint64_t len) { // bounds check uses log_size (= size), does NOT account for the offset if (unlikely(dev->log_size <= ((addr + len - 1) / VHOST_LOG_PAGE / 8))) return; page = addr / VHOST_LOG_PAGE; while (page * VHOST_LOG_PAGE < addr + len) { // writes to log_base[page/8] = (addr + off) + page/8 vhost_log_page((uint8_t *)(uintptr_t)dev->log_base, page); page += 1; } }

The bounds check validates page/8 < size but the actual write occurs at log_base + page/8 = addr + off + page/8. The maximum offset from the start of the mapping reached by this write is off + size - 1, while the mapping only covers size bytes. Writes exceed the mapped region by up to off bytes.

The same vulnerable pattern exists in __vhost_log_cache_sync() at lines 197–204.

Step 5 — Additional out-of-bounds madvise() call (line 2419):

mem_set_dump(dev, addr, size + off, false, alignment);

This calls madvise() on addr for size + off bytes, but only size bytes are mapped.

4.3 The Correct Pattern — Comparative Evidence

In vhost_user_mmap_region() (lines 1306, 1336, 1346), the same underlying operation is implemented correctly:

mmap_size = region->size + mmap_offset; mmap_addr = mmap(NULL, mmap_size, ..., region->fd, 0); // file offset = 0 region->host_user_addr = (uint64_t)(uintptr_t)mmap_addr + mmap_offset; // safe: within bounds

Here the total mapping covers size + offset bytes with file offset 0, so adding mmap_offset to the returned address safely falls within the mapped region. The vhost_user_set_log_base() function should follow this same pattern.

Additionally, the comment at line 2397 states "mmap from 0 to workaround a hugepage mmap bug", yet the code passes off as the file offset rather than
0. This discrepancy indicates the developer intended to replicate the correct pattern but introduced a logical error.

5. Why Existing Checks Are Insufficient

CheckLocationWhat It PreventsWhat It Missesif (off >= -size)Line 2386Integer overflow of off + sizeDoes not fix the double-application of offBounds check on log_sizevhost.c line 149Ensures page/8 < sizeDoes not account for log_base being off bytes past mapping start

6. Exploitation Conditions

All conditions required to reach the vulnerable code path are satisfied by default DPDK configurations:

ConditionEvidenceVHOST_F_LOG_ALL enabledDefault in VHOST_USER_NET_SUPPORTED_FEATURES (vhost_user.h, line 20)VHOST_USER_PROTOCOL_F_LOG_SHMFD enabledDefault in VHOST_USER_PROTOCOL_FEATURES (vhost_user.h, line 26)Attacker controls mmap_offsetVia VHOST_USER_SET_LOG_BASE message in the vhost-user protocolWrite triggerAny virtio ring activity (packet send/receive) activates vhost_log_write()

7. Impact

Memory Corruption: The write primitive performs a bitwise OR operation on memory at a controlled offset beyond the mapped region boundary. Depending on memory layout, this can corrupt adjacent mmap() regions, heap allocations, or internal DPDK data structures.

Attacker Control: The attacker controls the offset (off), the mapping size (size), and which bits are written via control over guest physical memory addresses used by virtio rings.

Potential Code Execution: If memory adjacent to the overwrite region contains function pointers such as notify_ops, backend_ops, or extern_ops within the vhost device structure, bit manipulation via OR operations may redirect execution flow.

Attack Vector: The attack originates entirely from within a guest VM and requires no host-level privileges.

8. Proof of Concept

Due to restrictions in my working environment (Ubuntu 22.04 VPS), I was unable to build or execute a proof-of-concept for this vulnerability. The environment does not permit establishing the vhost-user socket infrastructure and guest-host shared memory configuration required to exercise the full exploitation path.

9. Proposed Fix

Align vhost_user_set_log_base() with the correct pattern from vhost_user_mmap_region():

// BEFORE (vulnerable): addr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, off); dev->log_base = dev->log_addr + off; mem_set_dump(dev, addr, size + off, false, alignment); // AFTER (corrected): addr = mmap(0, size + off, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); dev->log_base = dev->log_addr + off; // now safe: within mapped region mem_set_dump(dev, addr, size + off, false, alignment);

Additionally, dev->log_size should reflect only the writable log region (size, not size + off), and the bounds check in __vhost_log_write() should be reviewed to account for the offset between log_addr and log_base.

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