Full Disclosure mailing list archives
OpenBSD mpls_do_error: Remote Kernel Stack Disclosure via MPLS Label Stack Over-read
From: shj
Date: Fri, 19 Jun 2026 07:32:14 +0200
------------------------------------------------------------------------OpenBSD mpls_do_error: Remote Kernel Stack Disclosure via MPLS Label Stack Over-read
------------------------------------------------------------------------ Affected: OpenBSD -current prior to 2026-06-18 (fixed in -current) Vendor: OpenBSD Severity: Medium Reporter: Argus Systems Date: 2026-06-12 CVE: CVE-2026-56099 1. SUMMARY ========== The mpls_do_error() function in sys/netmpls/mpls_input.c parses an incoming MPLS label stack into a fixed-size local array, struct shim_hdr stack[MPLS_INKERNEL_LOOP_MAX] (16 entries). When the parse loop completes without encountering the Bottom-of-Stack (BoS) label, nstk reaches MPLS_INKERNEL_LOOP_MAX (16). Several subsequent code paths then compute a copy length of (nstk + 1) * sizeof(*shim) -- 17 entries -- and use it with icmp_do_exthdr(), M_PREPEND(), and m_copyback() against the 16-entry stack object. This reads one struct shim_hdr (4 bytes) past the end of the array, and that data is reflected back to the sender inside the generated ICMP/MPLS error response. 2. AFFECTED VERSIONS ==================== The (nstk + 1) length computations against the 16-entry stack[] array were introduced with the ICMP/MPLS error path on 2010-09-13 (commit 201d6983add, "First shot at ICMP error handling inside an MPLS path. Currently only TTL exceeded errors for IPv4 are handled."). The parse loop was bounded by MPLS_INKERNEL_LOOP_MAX (16), but nothing rejected a stack that ran to completion without a BoS bit, so nstk could reach 16 and the subsequent (nstk + 1) reads accessed stack[16]. Affected: OpenBSD -current prior to 2026-06-18 (mpls_input.c pre v1.82). 3. DETAILS ========== Vulnerable code (sys/netmpls/mpls_input.c, mpls_do_error): struct shim_hdr stack[MPLS_INKERNEL_LOOP_MAX]; /* 16 entries */ ... for (nstk = 0; nstk < MPLS_INKERNEL_LOOP_MAX; nstk++) { ... stack[nstk] = *mtod(m, struct shim_hdr *); m_adj(m, sizeof(*shim)); if (MPLS_BOS_ISSET(stack[nstk].shim_label)) break; } /* no guard: with no BoS bit set, nstk == 16 here */ shim = &stack[0]; ... case IPVERSION: ... if (icmp_do_exthdr(m, ICMP_EXT_MPLS, 1, stack, (nstk + 1) * sizeof(*shim))) return (NULL); ... MPLS_INKERNEL_LOOP_MAX is defined as 16 and sizeof(struct shim_hdr) is 4. With nstk == 16, each of these copies 17 * 4 = 68 bytes from a 64-byte stack[] object, reading stack[16] -- one struct shim_hdr (4 bytes) of adjacent kernel stack -- and including it in the response. The same (nstk + 1) length is later used to prepend and m_copyback() the stack back onto the reflected packet: M_PREPEND(m, (nstk + 1) * sizeof(*shim), M_NOWAIT); ... m_copyback(m, 0, (nstk + 1) * sizeof(*shim), stack, M_NOWAIT); so the leaked entry also travels on the wire as the 17th MPLS shim header of the returned frame. 4. REACHABILITY =============== The path is reachable remotely via mpls_input() -> mpls_do_error() on systems that have MPLS enabled on an interface. The trigger is a crafted MPLS frame (EtherType 0x8847) carrying 16 labels with no BoS bit set and an outermost label TTL of 1, so the TTL-exceeded error path is taken: mpls_input (ttl <= 1) -> mpls_do_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0) The inner payload must be IPv4 so the IPVERSION branch is reached. 5. IMPACT ========= Each crafted packet leaks 4 bytes of kernel stack memory adjacent to the stack[] array. The leak is carried in the ICMP/MPLS extension object of the error response reflected back to the sender, so an attacker can harvest the leaked bytes. 6. PROOF OF CONCEPT =================== A Python/Scapy PoC sends a 16-label MPLS frame with no BoS bit set and an outermost label TTL of 1, then captures the reply. On a vulnerable kernel the reply carries 17 MPLS shim headers on the wire; the 17th (stack[16]) is the leaked kernel stack data. PoC: https://pop.argus-systems.ai/attachments/poc-008-mpls-stack-leak.py 7. FIX ====== Fixed in -current by mvs on 2026-06-18. The fix adds a guard that drops a label stack which runs to completion without a BoS bit, so nstk can no longer reach MPLS_INKERNEL_LOOP_MAX: if (nstk >= MPLS_INKERNEL_LOOP_MAX) { m_freem(m); return (NULL); } Fix commit (mpls_input.c v1.82): https://github.com/openbsd/src/commit/6a23123ec05f1eb29cfcaae0f3a468b2e1983cfd 8. TIMELINE =========== 2026-06-12 Reported to security () openbsd org with PoC 2026-06-18 Fix committed to -current 9. CREDIT ========= Discovered and reported by Argus Systems (https://byteray.co.uk/ 10. REFERENCES ============== Advisory: https://pop.argus-systems.ai/advisory/adv-040.html Proof of concept: https://pop.argus-systems.ai/attachments/poc-008-mpls-stack-leak.py Fix commit: https://github.com/openbsd/src/commit/6a23123ec05f1eb29cfcaae0f3a468b2e1983cfd _______________________________________________ Sent through the Full Disclosure mailing list https://nmap.org/mailman/listinfo/fulldisclosure Web Archives & RSS: https://seclists.org/fulldisclosure/
Current thread:
- OpenBSD mpls_do_error: Remote Kernel Stack Disclosure via MPLS Label Stack Over-read shj (Jun 20)
Sentinel — Human
This text functions as a highly detailed technical security bulletin, characterized by precise internal references and external evidence, strongly suggesting human authorship by a security researcher or vendor.
