Negative-length bcopy in ng_pptpgre ack processing: one in-window ack converts the DF-0596 window violation into an unbounded kernel OOB read+write
Summary
The ack handler computes index = ack - priv->recvAck - 1 (:641); the only bound is the window invariant xmitSeq-recvAck <= xmitWin <= 16, which the racy read-check-increment at :490-495 (DF-0596 TOCTOU family) can break to 17. One ordinary well-formed ack (ack == xmitSeq, passes both sanity checks) then yields index=16 and the window-shift bcopy at :668 receives length sizeof(pptptime_t) * (16 - 17) = (size_t)0xFFFFFFFFFFFFFFF8 - an unbounded OOB read AND write ending in a non-recoverable kernel page fault. Distinct sink/line from DF-0596's recorded 8-byte write at :514; CORRECTS its impact ceiling ('~200 bytes remaining... limited code-exec potential'): with index>=16 the copy is effectively infinite and the machine panics on the first such ack. In-guest bcopy_demo reproduces the exact kernel expression (recvAck=100 xmitSeq=117 ack=117 -> len=0xfffffffffffffff8). Remote peer of a root-configured PPTP session: after winning the SMP window TOCTOU once (two GRE packets on two CPUs), a single ack triggers it; blocked on this guest by CPU-0 serialization, open in ng_ether/SMP deployments. Fix: bounds-check index >= PPTP_XMIT_WIN -> badAck before use + the per-node serialization.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3520 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| bcopy_demo.c | trigger-source | userspace reproduction of the exact kernel length arithmetic | 3.1 KB | view raw |
| run.log | run-log | in-guest output | 632 B | view raw |
| VERDICT.md | verdict | derivation + reachability + fix | 4.1 KB | β raw |
| verdict.json | verdict | machine verdict | 2.4 KB | view raw |
DF-3520 β negative-length bcopy amplifier in ng_pptpgre ack processing
Verdict: NOT REPRODUCED in-kernel on this guest (needs the DF-0596 SMP TOCTOU first, which is blocked by the CPU-0 netisr serialization); the arithmetic sink is PROVEN and materially worse than DF-0596's recorded 8-byte OOB write.
status = not_reproduced, reproduced = 0, impact = none (on this
vector), confidence = certain (arithmetic + code).
The sink
sys/netgraph/pptpgre/ng_pptpgre.c, ack handler:
:641 const int index = ack - priv->recvAck - 1;
/* checks: PPTP_SEQ_DIFF(ack, xmitSeq) > 0 -> badAck (:646)
PPTP_SEQ_DIFF(ack, recvAck) <= 0 -> badAck (:650) */
:668 bcopy(a->timeSent + index + 1, a->timeSent,
sizeof(*a->timeSent) * (PPTP_XMIT_WIN - (index + 1)));
index is bounded only by the window invariant
xmitSeq - recvAck <= xmitWin <= PPTP_XMIT_WIN(16), which the
read-check-increment at :490-495 enforces non-atomically (the
DF-0596 TOCTOU family: two concurrent xmits both observe diff=15 < 16,
both increment β diff = 17). One well-formed ack (ack = xmitSeq)
then passes both sanity checks with
index = 17 - 0 - 1 = 16 PPTP_XMIT_WIN - (index + 1) = 16 - 17 = -1 sizeof(pptptime_t) * (-1) = (size_t)0xFFFFFFFFFFFFFFF8
β bcopy() of ~2^64 bytes: unbounded OOB read and write β
non-recoverable page fault in the kernel (certain panic), plus total
corruption of everything after timeSent[] in priv before the fault
lands. This is a distinct sink at a distinct line from DF-0596's
recorded primitive (the 8-byte OOB write at :514), and it corrects
DF-0596's impact ceiling ("~200 bytes remaining in the priv chunk,
limited"): with index β₯ 16 the copy length is not 8 bytes β it is
effectively infinite. Any xmitSeq-recvAck β₯ 17 (however produced)
converts one ordinary ack packet into this.
bcopy_demo.c (run in-guest, run.log) reproduces the exact kernel
expression and prints the length:
recvAck=100 xmitSeq=117 ack=117 -> index=16 int length = PPTP_XMIT_WIN - (index+1) = -1 size_t len = sizeof(pptptime_t) * (-1) = 0xfffffffffffffff8 kernel timeSent[] = pptptime_t[16] (128 bytes), index 16 is 8 bytes past end
(The --bcopy userspace attempt does not fault because glibc's
memcpy end-pointer arithmetic wraps modulo 2^64; the kernel's bcopy
walks forward through unmapped memory and faults β either way the
observation is the length computation, which is identical in kernel
and userspace.)
Reachability
Requires the DF-0596 TOCTOU (or any equivalent window-violation race)
first β single-threaded, the invariant holds and index β€ 15
(recvAck only grows; stale-read analysis in the audit shows the
recv-side alone cannot exceed 15). The TOCTOU needs two GRE packets in
ng_pptpgre_recv/xmit on two CPUs β blocked from userspace ng_socket
by the CPU-0 netisr serialization (DF-0590 Β§2), open in the
ng_ether/SMP VPN-concentrator deployment. The remote peer then only
needs to send the ack for the highest outstanding sequence number β
cid must be known (legit session peer, or the DF-0596-noted 65536
brute).
Fix
Bounds-check index before use (defense in depth, independent of
DF-0596's serialization fix):
--- a/sys/netgraph/pptpgre/ng_pptpgre.c
+++ b/sys/netgraph/pptpgre/ng_pptpgre.c
@@ -649,6 +649,11 @@
if (PPTP_SEQ_DIFF(ack, priv->recvAck) <= 0)
goto badAck; /* ack already timed out */
+ if (ack - priv->recvAck - 1 >= PPTP_XMIT_WIN)
+ goto badAck; /* window invariant violated */
priv->recvAck = ack;
plus the per-node serialization fix (DF-0590's recommendation) so the invariant cannot break in the first place.
Files
bcopy_demo.c,run.logβ arithmetic reproduction (in-guest)
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
run.log (bcopy_demo: index=16, int length=-1, size_t len=0xfffffffffffffff8); VERDICT.md full derivation incl. why the recv-side alone cannot exceed index 15 and why the sink is distinct from DF-0596's :514 write
PoC changes
Authored from scratch; the userspace --bcopy attempt intentionally does not fault under glibc (memcpy end-pointer wraps mod 2^64) - the deliverable is the length computation itself, identical in kernel and userspace; wording kept honest in README/VERDICT.
Verified recommended fix
Bounds-check index against PPTP_XMIT_WIN before use at :641-:668 (goto badAck) plus the per-node serialization fix so the invariant cannot break.
Verdict
NOT REPRODUCED in-kernel on this guest, arithmetic certain: once the xmitSeq-recvAck window invariant breaks past 17 via the DF-0596 TOCTOU family (racy window check ng_pptpgre.c:490-495), one ordinary well-formed ack produces index=16 at :641 and the window-shift bcopy at :668 receives length sizeof(pptptime_t)*(16-17) = (size_t)0xFFFFFFFFFFFFFFF8 - an unbounded kernel OOB read+write and certain fatal page fault, far beyond DF-0596's recorded 8-byte OOB write impact ceiling. The userspace bcopy_demo reproduces the exact kernel length computation in-guest (run.log). In-kernel manifestation inherits DF-0596's reachability: SMP-only, blocked from userspace ng_socket by CPU-0 netisr serialization, open in the ng_ether/SMP deployment where the peer needs only a valid cid.
No comments yet.