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>
This commit is contained in:
Brad Fitzpatrick
2026-04-10 20:31:35 -07:00
committed by Brad Fitzpatrick
parent a0a8fae856
commit 943b426038
2 changed files with 43 additions and 2 deletions
+7 -2
View File
@@ -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
}
+36
View File
@@ -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)
}
}