]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
tests: shell: add test case for interval set with timeout and aborted transaction
authorFlorian Westphal <fw@strlen.de>
Wed, 28 Jan 2026 23:07:18 +0000 (00:07 +0100)
committerFlorian Westphal <fw@strlen.de>
Thu, 29 Jan 2026 17:34:05 +0000 (18:34 +0100)
Add a regression test for rbtree+bsearch getting out-of-sync in
nf-next kernel.

This covers the syzkaller reproducer from
https://syzkaller.appspot.com/bug?extid=d417922a3e7935517ef6
which triggers abort with earlier gc at insert time and additional corner
case where transaction passes without recording a relevant change in the set
(i.e. no call to either abort or commit).

This test passes even on buggy kernels unless KASAN is enabled.

Signed-off-by: Florian Westphal <fw@strlen.de>
tests/shell/testcases/sets/dumps/rbtree_timeout_no_commit.json-nft [new file with mode: 0644]
tests/shell/testcases/sets/dumps/rbtree_timeout_no_commit.nft [new file with mode: 0644]
tests/shell/testcases/sets/rbtree_timeout_no_commit [new file with mode: 0755]

diff --git a/tests/shell/testcases/sets/dumps/rbtree_timeout_no_commit.json-nft b/tests/shell/testcases/sets/dumps/rbtree_timeout_no_commit.json-nft
new file mode 100644 (file)
index 0000000..3cf2267
--- /dev/null
@@ -0,0 +1,34 @@
+{
+  "nftables": [
+    {
+      "metainfo": {
+        "version": "VERSION",
+        "release_name": "RELEASE_NAME",
+        "json_schema_version": 1
+      }
+    },
+    {
+      "table": {
+        "family": "ip",
+        "name": "t",
+        "handle": 0
+      }
+    },
+    {
+      "set": {
+        "family": "ip",
+        "name": "s",
+        "table": "t",
+        "type": "ipv4_addr",
+        "handle": 0,
+        "flags": [
+          "interval",
+          "timeout"
+        ],
+        "elem": [
+          "10.0.0.1"
+        ]
+      }
+    }
+  ]
+}
diff --git a/tests/shell/testcases/sets/dumps/rbtree_timeout_no_commit.nft b/tests/shell/testcases/sets/dumps/rbtree_timeout_no_commit.nft
new file mode 100644 (file)
index 0000000..df0be9a
--- /dev/null
@@ -0,0 +1,7 @@
+table ip t {
+       set s {
+               type ipv4_addr
+               flags interval,timeout
+               elements = { 10.0.0.1 }
+       }
+}
diff --git a/tests/shell/testcases/sets/rbtree_timeout_no_commit b/tests/shell/testcases/sets/rbtree_timeout_no_commit
new file mode 100755 (executable)
index 0000000..38f5afc
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+# Test for bug added with kernel commit
+# 7e43e0a1141d ("netfilter: nft_set_rbtree: translate rbtree to array for binary search")
+# where the binary search blob gets out-of-sync with the rbtree, holding pointers
+# to elements that have been free'd by garbage collection.
+#
+# 1. add new element, transaction is later aborted.
+# Commit hook isn't called, so make sure ->abort refreshes the blob too.
+#
+# 2. re-add an existing element, transaction passes.
+# In this case, the commit hook isn't called because we don't have
+# any changes to the set from transaction point of view.
+# Transaction log can even be empty in this case.
+#
+# 3. create (F_EXCL) an existing element.
+# Also triggers abort, but set sets ->abort callback isn't invoked
+# as no element was added.
+
+$NFT -f - <<EOF
+table t {
+       set s {
+               type ipv4_addr
+               flags interval, timeout
+               elements = { 10.0.0.1, 10.0.1.2-10.1.2.4 timeout 100ms }
+       }
+}
+EOF
+
+sleep 1
+
+$NFT add element ip t s { 10.0.0.1 } || exit 1
+
+# The above insert triggered GC on the existing element,
+# and the 'add' suceeded (transaction successful).
+# 'get element' must fail and not encounter the removed
+# element.
+$NFT get element ip t s '{ 10.0.1.2 }' && exit 1
+echo "PASS: Did not find expired element after re-adding existing element"
+
+# Re-add an expiring element
+$NFT add element ip t s '{ 10.0.1.2 timeout 100ms }' || exit 1
+sleep 1
+
+$NFT -f - <<EOF
+add element ip t s { 10.0.0.3, 10.0.1.5-10.0.1.42 }
+add element inet t s { 10.0.0.3 }
+EOF
+[ $? -eq 0 ] && exit 1
+
+$NFT get element ip t s '{ 10.0.1.2 }' && exit 1
+echo "PASS: Did not find expired element after transaction abort"
+
+# Re-add an expiring element
+$NFT add element ip t s '{ 10.0.1.2 timeout 100ms }' || exit 1
+sleep 1
+
+# This create must fail, the transaction is aborted, abort callback
+# won't be executed as no changes happened in this set.
+$NFT create element ip t s { 10.0.0.1 } && exit 1
+
+$NFT get element ip t s '{ 10.0.1.2 }' && exit 1
+echo "PASS: Did not find expired element after create failure"