From: bugzilla@dpdk.org
To: dev@dpdk.org
Subject: [DPDK/vhost/virtio Bug 2004] vhost log mapping double offset
Date: Fri, 28 Aug 2026 13:13:42 +0000 [thread overview]
Message-ID: <bug-2004-3@https.bugs.dpdk.org/> (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: dev@dpdk.org
Reporter: thomas@monjalon.net
Target Milestone: ---
Group: security
Report date: 2026-04-29
Reported by: Don Salvatore Nero <hemo40047@gmail.com>
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.
reply other threads:[~2026-08-28 13:13 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=bug-2004-3@https.bugs.dpdk.org/ \
--to=bugzilla@dpdk.org \
--cc=dev@dpdk.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.