Add a netfilter kselftest for the nft_ct timeout object destroy race
fixed by commit f8dca15a1b19 ("netfilter: nft_ct: fix use-after-free in
timeout object destroy").Keep creating new TCP connections from one namespace while repeatedly flushing and recreating the table that owns a ct timeout object. This exercises concurrent packet processing against the timeout object teardown path without requiring external traffic tools beyond bash, nft and ip. On a KASAN kernel, a regression in the RCU lifetime handling should show up as a slab-use-after-free report in nf_conntrack_tcp_packet(). Assisted-by: GitHub Copilot:claude-sonnet-4-6 Signed-off-by: Vastargazing <[email protected]> --- .../testing/selftests/net/netfilter/Makefile | 1 + .../netfilter/nft_ct_timeout_concurrency.sh | 116 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 tools/testing/selftests/net/netfilter/nft_ct_timeout_concurrency.sh diff --git a/tools/testing/selftests/net/netfilter/Makefile b/tools/testing/selftests/net/netfilter/Makefile index ee2d1a5254f8..bcf53a1ef7ec 100644 --- a/tools/testing/selftests/net/netfilter/Makefile +++ b/tools/testing/selftests/net/netfilter/Makefile @@ -25,6 +25,7 @@ TEST_PROGS := \ nft_audit.sh \ nft_concat_range.sh \ nft_conntrack_helper.sh \ + nft_ct_timeout_concurrency.sh \ nft_fib.sh \ nft_flowtable.sh \ nft_interface_stress.sh \ diff --git a/tools/testing/selftests/net/netfilter/nft_ct_timeout_concurrency.sh b/tools/testing/selftests/net/netfilter/nft_ct_timeout_concurrency.sh new file mode 100644 index 000000000000..79876cdfb2df --- /dev/null +++ b/tools/testing/selftests/net/netfilter/nft_ct_timeout_concurrency.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Stress nftables ct timeout object destruction while new TCP flows keep +# attaching the object. + +net_netfilter_dir=$(dirname "$(readlink -e "${BASH_SOURCE[0]}")") +source "$net_netfilter_dir/lib.sh" + +checktool "nft --version" "run test without nft tool" + +read kernel_tainted < /proc/sys/kernel/tainted + +# Default to 80% of the global timeout but keep this stress test short. +TEST_RUNTIME=$((${kselftest_timeout:-30} * 8 / 10)) +[[ $TEST_RUNTIME -gt 20 ]] && TEST_RUNTIME=20 + +PORT=12345 + +cleanup() +{ + cleanup_all_ns +} + +load_ruleset() +{ + ip netns exec "$ns1" nft -f - <<EOF +table ip ct_test { + ct timeout tcptime { + protocol tcp + policy = { established: 5s } + } + + chain output { + type filter hook output priority filter; policy accept; + ct state new ip daddr 10.0.1.2 tcp dport $PORT counter ct timeout set "tcptime" + } +} +EOF +} + +flush_table() +{ + ip netns exec "$ns1" nft flush table ip ct_test 2>/dev/null || true + ip netns exec "$ns1" nft delete table ip ct_test 2>/dev/null || true +} + +rule_packets() +{ + local packets + + packets=$(ip netns exec "$ns1" nft list chain ip ct_test output 2>/dev/null | + sed -n 's/.*counter packets \([0-9][0-9]*\) bytes.*/\1/p' | + head -n1) + + if [ -n "$packets" ]; then + echo "$packets" + else + echo 0 + fi +} + +trap cleanup EXIT + +setup_ns ns1 ns2 || exit $ksft_skip + +if ! ip link add veth0 netns "$ns1" type veth peer name veth0 netns "$ns2" > /dev/null 2>&1; then + echo "SKIP: No virtual ethernet pair device support in kernel" + exit $ksft_skip +fi + +ip -net "$ns1" link set veth0 up +ip -net "$ns2" link set veth0 up + +ip -net "$ns1" addr add 10.0.1.1/24 dev veth0 +ip -net "$ns2" addr add 10.0.1.2/24 dev veth0 + +if ! load_ruleset; then + echo "SKIP: Could not load ct timeout ruleset" + exit $ksft_skip +fi + +ip netns exec "$ns1" bash -c ' + while :; do + exec 3<>/dev/tcp/10.0.1.2/'"$PORT"' 2>/dev/null || true + exec 3<&- 3>&- + done +' > /dev/null 2>&1 & +traffic_pid=$! + +if ! busywait_for_counter "$BUSYWAIT_TIMEOUT" 1 rule_packets > /dev/null; then + echo "FAIL: Did not observe TCP traffic hitting ct timeout rule" + exit $ksft_fail +fi + +end_time=$((SECONDS + TEST_RUNTIME)) +while [ "$SECONDS" -lt "$end_time" ]; do + flush_table + + if ! load_ruleset; then + echo "FAIL: Could not recreate ct timeout ruleset" + exit $ksft_fail + fi +done + +flush_table + +kill "$traffic_pid" 2>/dev/null +wait "$traffic_pid" 2>/dev/null + +if [[ $kernel_tainted -eq 0 && $(</proc/sys/kernel/tainted) -ne 0 ]]; then + echo "FAIL: Kernel is tainted" + exit $ksft_fail +fi + +exit $ksft_pass -- 2.51.0

