summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2026-04-10 20:31:35 -0700
committerBrad Fitzpatrick <brad@danga.com>2026-04-14 07:45:01 -0700
commit943b42603814c58e7d6c7a629ee7b71f9a011eca (patch)
tree49a2d0448cbbe1120d765dd8a93790b0ac9a2c10
parenta0a8fae8566fc41018faff5162d955e25f99ab4d (diff)
downloadtailscale-943b42603814c58e7d6c7a629ee7b71f9a011eca.tar.xz
tailscale-943b42603814c58e7d6c7a629ee7b71f9a011eca.zip
util/linuxfw: fix nil deref in nftables chain check
Fix a panic in getOrCreateChain when the kernel lacks nftables support (CONFIG_NF_TABLES). When the nftables netlink connection fails, chain objects returned by getChainFromTable can have nil Hooknum and Priority fields. Dereferencing these caused tailscaled to SIGSEGV during router configuration, which manifested as tailscaled silently crashing ~13 seconds after "tailscale up" on arm64 gokrazy (whose kernel.arm64 build doesn't include nftables). Updates #13038 Change-Id: I14433616da5ed57895cad37038921fb4f79c3534 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--util/linuxfw/nftables_runner.go9
-rw-r--r--util/linuxfw/nftables_runner_test.go36
2 files changed, 43 insertions, 2 deletions
diff --git a/util/linuxfw/nftables_runner.go b/util/linuxfw/nftables_runner.go
index cdb1c5bfb..074a3d47c 100644
--- a/util/linuxfw/nftables_runner.go
+++ b/util/linuxfw/nftables_runner.go
@@ -453,8 +453,13 @@ func getOrCreateChain(c *nftables.Conn, cinfo chainInfo) (*nftables.Chain, error
// type/hook/priority, but for "conventional chains" assume they're what
// we expect (in case iptables-nft/ufw make minor behavior changes in
// the future).
- if isTSChain(chain.Name) && (chain.Type != cinfo.chainType || *chain.Hooknum != *cinfo.chainHook || *chain.Priority != *cinfo.chainPriority) {
- return nil, fmt.Errorf("chain %s already exists with different type/hook/priority", cinfo.name)
+ if isTSChain(chain.Name) {
+ if chain.Hooknum == nil || chain.Priority == nil {
+ return nil, errors.New("nftables chain has nil hooknum or priority; kernel may lack nftables support (CONFIG_NF_TABLES)")
+ }
+ if chain.Type != cinfo.chainType || *chain.Hooknum != *cinfo.chainHook || *chain.Priority != *cinfo.chainPriority {
+ return nil, fmt.Errorf("chain %s already exists with different type/hook/priority", cinfo.name)
+ }
}
return chain, nil
}
diff --git a/util/linuxfw/nftables_runner_test.go b/util/linuxfw/nftables_runner_test.go
index 58a1f96ed..6fd244413 100644
--- a/util/linuxfw/nftables_runner_test.go
+++ b/util/linuxfw/nftables_runner_test.go
@@ -1309,3 +1309,39 @@ func TestMakeConnmarkSaveExprs(t *testing.T) {
t.Fatalf("Flush() failed: %v", err)
}
}
+
+// TestGetOrCreateChainNilHooknum verifies that getOrCreateChain returns a clear
+// error when a ts- chain exists but has nil Hooknum/Priority, which happens when
+// the kernel lacks nftables support (CONFIG_NF_TABLES).
+func TestGetOrCreateChainNilHooknum(t *testing.T) {
+ conn := newSysConn(t)
+
+ table := conn.AddTable(&nftables.Table{
+ Family: nftables.TableFamilyIPv4,
+ Name: "ts-filter-test",
+ })
+ // Add a ts- chain without hooknum/priority (regular chain), simulating
+ // the broken state returned by a kernel without nftables support.
+ conn.AddChain(&nftables.Chain{
+ Name: "ts-input",
+ Table: table,
+ })
+ if err := conn.Flush(); err != nil {
+ t.Fatalf("Flush() failed: %v", err)
+ }
+
+ // Now try getOrCreateChain expecting a base chain with hooknum/priority.
+ _, err := getOrCreateChain(conn, chainInfo{
+ table: table,
+ name: "ts-input",
+ chainType: nftables.ChainTypeFilter,
+ chainHook: nftables.ChainHookInput,
+ chainPriority: nftables.ChainPriorityFilter,
+ })
+ if err == nil {
+ t.Fatal("expected error for chain with nil hooknum/priority, got nil")
+ }
+ if !strings.Contains(err.Error(), "nil hooknum") {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}