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.
--
You are receiving this mail because:
You are the assignee for the bug.