Two ways out of the box: memory-safety defects in libslirp’s NC-SI and DHCPv6 paths
libslirp is the user-mode TCP/IP stack behind QEMU’s default networking and, through slirp4netns, behind rootless Podman and Docker. It parses guest-originated traffic inside the host process. Two defects were found in paths the project does not fuzz: an out-of-bounds read that reflects host memory back to the guest, and an attacker-controlled heap write. Both were reported with working reproducers and accepted by the maintainer within hours.
PSIRTSUPT-23997), and both fixes shipped upstream in libslirp 4.9.5, tagged the same day. Red Hat records on each advisory: “Red Hat would like to thank Stuart Thomas for reporting this issue.” These are credited disclosures, not a paid bounty. This page was published after the fix shipped.
Why libslirp is worth looking at
libslirp is not an end product. It is shared plumbing, and that is precisely what makes it interesting. It provides “user networking” — the mode you get from -netdev user without asking for it — and it runs in the host process, parsing packets that the guest composes.
| Product | Uses libslirp for | Boundary crossed |
|---|---|---|
| QEMU | -netdev user, the default VM networking mode | guest → host |
| Podman / rootless Docker | container networking via slirp4netns | container → host |
| KubeVirt | VM networking on Kubernetes | guest → host |
| UTM | QEMU front-end on macOS and iOS | guest → host |
The threat model is therefore the isolation boundary itself: the attacker already has code execution inside the guest or container, and the asset is the host process. A single upstream fix propagates to all of the above as distributions rebuild the library — which is also why Red Hat PSIRT tracked the report, since RHEL ships libslirp beneath QEMU, KubeVirt and Podman.
CVE-2026-95507 — NC-SI OEM out-of-bounds read
NC-SI is a sideband management protocol. libslirp implements enough of it to answer a guest’s queries, and ncsi_input() validates an incoming frame against the 16-byte NC-SI common header — the check added in response to CVE-2020-29129:
/* src/ncsi.c:287 — minimum 30 bytes */
if (pkt_len < ETH_HLEN + sizeof(struct ncsi_pkt_hdr)) {
return; /* packet too short */
}
The OEM command handler was added later. It reads four bytes at NC-SI payload offset 16 — frame offset 30 — and copies them into the reply before testing the manufacturer id:
/* src/ncsi.c:130-139 — ncsi_rsp_handler_oem() */
uint32_t mfr_id = ntohl(cmd->mfr_id); /* reads 4 bytes past a 30-byte frame */
...
rsp->mfr_id = cmd->mfr_id; /* copied into the reply, then sent */
A 30-byte OEM frame satisfies the common-header check but carries no mfr_id field at all. Those four bytes are read out of bounds and reflected to the guest. That is the unconditional case. Where the host is configured to present as Mellanox (mfr_id = 0x8119), the sub-handler reads cmd->data at offset 20 as well, so a 34–37 byte frame over-reads there too.
What makes the path reachable is that NC-SI receives the raw receive buffer, with no intervening mbuf copy — only pkt_len >= 14 is guaranteed:
/* src/slirp.c:1313-1314 */
case ETH_P_NCSI:
ncsi_input(slirp, pkt, pkt_len);
Under a guard-page allocator, a 30-byte OEM frame (ethertype 0x88F8, type 0x50) faults exactly at the read, at ncsi.c:136, reached from ncsi_input and slirp_input. The disclosure was then demonstrated allocator-independently: sentinel bytes placed immediately after the frame came back inside the NC-SI reply, with the reflected mfr_id reading 41 41 41 41. Host memory, returned to the guest.
Impact. Roughly four bytes of adjacent host memory per frame, repeatable, rising to about eight where Mellanox is configured. Red Hat rates it Moderate, CVSS 3.1 4.3 (AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N). As a primitive it is worth more than that: it discloses heap pointers, and defeating ASLR in the host process is the usual first step of something larger.
CVE-2026-95508 — DHCPv6 and TFTP response-builder heap overflow
dhcpv6_info_request() builds its reply into an mbuf whose data area is sized to the interface MTU, then copies the guest-supplied CLIENTID into it. The CLIENTID is bounded — but only to 256 bytes, with no check that the result fits the buffer it is being written into:
/* src/dhcpv6.c:164 — no fit check */
memcpy(resp, ri.client_id, ri.client_id_len);
When the interface MTU is small, the reply mbuf is far smaller than 256 bytes and a large CLIENTID overflows it with attacker-chosen bytes. The threshold is exact: the overflow occurs whenever client_id_len > if_mtu - 8. With if_mtu = 68 — IF_MTU_MIN, which libslirp accepts — and a 256-byte CLIENTID, a DHCPv6 INFO_REQUEST faults inside the copy. At the default MTU of 1500 it is clean.
A second instance sits in the same function. The boot-URL option computes a signed smaxlen that can go negative; passed to slirp_fmt() as a size_t it becomes enormous and defeats the truncation it was supposed to enforce. The whole path is reachable over IPv6/UDP to port 547.
Impact. An attacker-controlled heap overwrite is a heap-corruption primitive. Paired with the NC-SI leak above it is the classic information-leak-plus-write combination that a guest→host escape attempt is built on. That said, the precondition is real and it is not the default: this needs IPv6 enabled and an interface MTU below roughly 264 bytes. Red Hat rates it Important, CVSS 3.1 7.4 (AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H) — the high attack complexity is carried in the vector rather than discounted from the score. The same unbounded-write pattern affects the TFTP response builder, and Red Hat’s advisory covers both.
Where the bugs were, and why they were there
Both defects sit in code that the project does not fuzz. NC-SI ships no upstream harness at all. That was the selection principle rather than an accident: residual bugs survive where coverage is thin, and the NC-SI path had already produced one CVE whose fix — a single length check against the common header — was correct for the code that existed in 2020 and was quietly outgrown when the OEM handler arrived.
That is the more general lesson. A bounds check placed at a protocol’s front door ages badly if later handlers reach deeper into the packet than the door was built to guarantee. The upstream remedy is structural rather than a patch to one memcpy: thread the packet length into the command handlers so that each validates its own body before dereferencing anything past the common header.
The harness was AFL++ with the libdislocator guard-page allocator, running on a Raspberry Pi 400 — aarch64, NEON. The guard page is the part that matters: it turns any out-of-bounds heap access into an immediate fault, so a one-byte over-read is caught rather than absorbed silently into whatever happens to be next in the heap. A four-byte over-read of the kind in CVE-2026-95507 is invisible to an ordinary fuzzing run.
Disclosure
| Date | Event |
|---|---|
| 2026-09-16 | Static audit and fuzzing of the NC-SI path; both defects reproduced under the guard-page allocator. |
| 2026-09-16 | Reported to the libslirp maintainer and to Red Hat PSIRT, with reproducers and candidate patches. |
| 2026-09-16 | Maintainer review; revised patches — per-handler length checks, and MTU bounds on every DHCPv6 option write. |
| 2026-09-16 | Both patches accepted, approximately five hours after the report. Scheduled for libslirp 4.9.5. |
| 2026-09-22 | CVE-2026-95507 and CVE-2026-95508 assigned, coordinated through Red Hat Product Security (PSIRTSUPT-23997). |
| 2026-09-22 | libslirp 4.9.5 released carrying both fixes. This case study published. |
Five hours from report to accepted patch is an unusually good vendor response, and it is worth saying so plainly. The reason it was possible is not generosity: the report arrived with a traced root cause, a reproducer, and a patch, so the maintainer’s job was review rather than investigation.
Remediation
Upgrade to libslirp 4.9.5, released 22 September 2026, or apply the merged patches to 4.9.4. Both fixes are additive length checks with no behavioural change for valid traffic, and the upstream test suite (test/ncsitest) continues to pass.
Interim mitigation is available for the overflow only: do not configure an interface MTU below the IPv6 minimum of 1280 while IPv6 is enabled. The NC-SI read has no configuration toggle. There is no setting that turns it off, and the code fix is the only remedy.
The other half of the campaign, which found nothing
The same guard-page method was applied in parallel to Google Highway’s vqsort — a SIMD vectorised quicksort that ships no in-tree fuzzer and whose ARM NEON paths see little of Google’s largely x86-centric CI fuzzing. Ten harnesses covered the sort, quickselect and partial-sort entry points across scalar and key-value types.
The result was clean. Over 1.2 billion executions and thousands of completed corpus cycles produced zero crashes. The harnesses were verified faithful beforehand — no false positives on valid input — so the negative result means something: vqsort’s masked-remainder handling and base-case sorting networks hold up on ARM. Had a defect turned up it would have been eligible for Google’s Open Source VRP. It is recorded here because a method that only ever reports its successes is not a method, and the harness is reusable against SVE and x86.