β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3520

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3520 Β· 4 files
FileTypeDescriptionSize
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
VERDICT.md verdict derivation + reachability + fix
↓ download 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_testable
per-fix-DF-3520

Confirmed 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.