From 051d8d6f0538abf483c762db8dd52771447abb29 Mon Sep 17 00:00:00 2001 From: Ville Vesilehto Date: Fri, 19 Sep 2025 05:15:40 +0300 Subject: [PATCH] fix(plugin): normalize panics on invalid origins (#7563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously OriginsFromArgsOrServerBlock accessed the output of NormalizeExact() by index 0, which could panic when normalization returned an empty slice on error. This happens with malformed input surfaced by fuzzing, for example "unix://". This change hardens normalization in the server block path. If normalization yields no entries, the original value is preserved. The function still returns a newly copied slice. This preserves legacy semantics for valid inputs while eliminating the crash on malformed ones. Added tests to validate. Signed-off-by: Ville Vesilehto --- plugin/normalize.go | 6 +++- plugin/normalize_test.go | 62 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/plugin/normalize.go b/plugin/normalize.go index 4b92bb43e..1c596ee19 100644 --- a/plugin/normalize.go +++ b/plugin/normalize.go @@ -179,7 +179,11 @@ func OriginsFromArgsOrServerBlock(args, serverblock []string) []string { s := make([]string, len(serverblock)) copy(s, serverblock) for i := range s { - s[i] = Host(s[i]).NormalizeExact()[0] // expansion of these already happened in dnsserver/register.go + sx := Host(s[i]).NormalizeExact() // expansion of these already happened in dnsserver/register.go + if len(sx) == 0 { + continue + } + s[i] = sx[0] } return s } diff --git a/plugin/normalize_test.go b/plugin/normalize_test.go index cc32eaea4..78ef4098c 100644 --- a/plugin/normalize_test.go +++ b/plugin/normalize_test.go @@ -1,6 +1,7 @@ package plugin import ( + "reflect" "sort" "testing" ) @@ -138,3 +139,64 @@ func TestHostNormalizeExact(t *testing.T) { } } } + +func TestOriginsFromArgsOrServerBlock(t *testing.T) { + tests := []struct { + name string + args []string + serverblock []string + expected []string + }{ + { + name: "args", + args: []string{"example.org"}, + serverblock: []string{"ignored.local"}, + expected: []string{"example.org."}, + }, + { + name: "args with cidr expands", + args: []string{"10.0.0.0/15"}, + serverblock: nil, + expected: []string{"0.10.in-addr.arpa.", "1.10.in-addr.arpa."}, + }, + { + name: "serverblock first normalized", + args: nil, + serverblock: []string{"example.org"}, + expected: []string{"example.org."}, + }, + { + name: "serverblock cidr first only", + args: nil, + serverblock: []string{"10.0.0.0/15"}, + expected: []string{"0.10.in-addr.arpa."}, + }, + { + name: "serverblock invalid utf-8 preserved", + args: nil, + serverblock: []string{"\xFF\n:", "example.org"}, + expected: []string{"\xFF\n:", "example.org."}, + }, + { + name: "args invalid utf-8 dropped", + args: []string{"\xFF\n:", "example.org"}, + serverblock: nil, + expected: []string{"example.org."}, + }, + { + name: "serverblock invalid utf-8 with prefix", + args: nil, + serverblock: []string{"unix://\xff\netcd", "example.org"}, + expected: []string{"\uFFFD\netcd.", "example.org."}, // \uFFFD is the replacement character + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := OriginsFromArgsOrServerBlock(tt.args, tt.serverblock) + if !reflect.DeepEqual(got, tt.expected) { + t.Fatalf("%s: expected %q, got %q", tt.name, tt.expected, got) + } + }) + } +}