From c483bebba8ceadd9ac9ca8e81d9c86c5c6d6d500 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 7 Jun 2026 07:37:50 +0000 Subject: [PATCH] mapper: guard nil Hostinfo in addNextDNSMetadata addNextDNSMetadata dereferenced node.Hostinfo().OS() without the .Valid() guard its siblings (RequestTags, TailNode) apply, so building the DNS config for a node with nil Hostinfo (a legacy NULL host_info row) panicked whenever a NextDNS resolver was configured. Guard the OS() dereference. --- hscontrol/mapper/mapper.go | 8 +++++++- hscontrol/mapper/mapper_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/hscontrol/mapper/mapper.go b/hscontrol/mapper/mapper.go index 82aed5af..d52200eb 100644 --- a/hscontrol/mapper/mapper.go +++ b/hscontrol/mapper/mapper.go @@ -256,7 +256,13 @@ func addNextDNSMetadata(resolvers []*dnstype.Resolver, node types.NodeView) { q := u.Query() q.Set("device_name", node.Hostname()) - q.Set("device_model", node.Hostinfo().OS()) + + // Guard Hostinfo().Valid() before dereferencing OS(): a node loaded + // from a legacy NULL host_info row has a nil Hostinfo, and OS() would + // panic. Mirrors the .Valid() guard in RequestTags/TailNode. + if node.Hostinfo().Valid() { + q.Set("device_model", node.Hostinfo().OS()) + } if ips := node.IPs(); len(ips) > 0 { q.Set("device_ip", ips[0].String()) diff --git a/hscontrol/mapper/mapper_test.go b/hscontrol/mapper/mapper_test.go index 236c0094..b8baefc6 100644 --- a/hscontrol/mapper/mapper_test.go +++ b/hscontrol/mapper/mapper_test.go @@ -350,3 +350,27 @@ func TestBuildFromChangeFiltersUserProfilesByVisibility(t *testing.T) { "n1 must not receive user2's profile; n2 is not ACL-visible to n1") } } + +// TestGenerateDNSConfigNilHostinfoNoPanic proves generateDNSConfig does not +// panic when a node's Hostinfo is nil (e.g. a legacy DB row with a NULL +// host_info column). addNextDNSMetadata dereferenced node.Hostinfo().OS() +// without the .Valid() guard its siblings (RequestTags, TailNode) apply, so +// building such a node's map crashed the server whenever a NextDNS resolver +// was configured. +func TestGenerateDNSConfigNilHostinfoNoPanic(t *testing.T) { + node := (&types.Node{ + Hostname: "legacy-node", + IPv4: iap("100.64.0.1"), + // Hostinfo intentionally nil, as a legacy NULL host_info row loads. + }).View() + + cfg := &types.Config{ + TailcfgDNSConfig: &tailcfg.DNSConfig{ + Resolvers: []*dnstype.Resolver{{Addr: "https://dns.nextdns.io/abc"}}, + }, + } + + require.NotPanics(t, func() { + generateDNSConfig(cfg, node, nil) + }, "generateDNSConfig must not panic when a node has nil Hostinfo") +}