dns/dnsmessage: correctly validate SVCB record parameter order

Fix a bug which caused us to not properly validate the order
of parameters in SVCB/HTTPS records.

Fixes golang/go#79796

Change-Id: I9d3c43bde06f3d25786a5b89e45613e76a6a6964
Reviewed-on: https://go-review.googlesource.com/c/net/+/786346
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Nicholas Husin <nsh@golang.org>
Reviewed-by: Nicholas Husin <husin@google.com>
diff --git a/dns/dnsmessage/message_test.go b/dns/dnsmessage/message_test.go
index e5997dc..9e46c63 100644
--- a/dns/dnsmessage/message_test.go
+++ b/dns/dnsmessage/message_test.go
@@ -1940,6 +1940,30 @@
 				0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x5d,
 			},
 		},
+		{
+			name: "SVCB out-of-order keys",
+			in: []byte{
+				0x12, 0x34, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+				0x00, 0x00, 0x41, 0x00, 0x01,
+				0x00, 0x00, 0x41, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+				0x00, 0x0b,
+				0x00, 0x01, 0x00,
+				0x00, 0x02, 0x00, 0x00,
+				0x00, 0x01, 0x00, 0x00,
+			},
+		},
+		{
+			name: "SVCB duplicate keys",
+			in: []byte{
+				0x12, 0x34, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+				0x00, 0x00, 0x41, 0x00, 0x01,
+				0x00, 0x00, 0x41, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+				0x00, 0x0b,
+				0x00, 0x01, 0x00,
+				0x00, 0x01, 0x00, 0x00,
+				0x00, 0x01, 0x00, 0x00,
+			},
+		},
 	} {
 		t.Run(test.name, func(t *testing.T) {
 			var p Parser
diff --git a/dns/dnsmessage/svcb.go b/dns/dnsmessage/svcb.go
index 252401b..7729378 100644
--- a/dns/dnsmessage/svcb.go
+++ b/dns/dnsmessage/svcb.go
@@ -168,9 +168,8 @@
 	if err != nil {
 		return oldMsg, &nestedError{"SVCBResource.Target", err}
 	}
-	var previousKey SVCParamKey
 	for i, param := range r.Params {
-		if i > 0 && param.Key <= previousKey {
+		if i > 0 && param.Key <= r.Params[i-1].Key {
 			return oldMsg, &nestedError{"SVCBResource.Params", errParamOutOfOrder}
 		}
 		if len(param.Value) > (1<<16)-1 {
@@ -220,6 +219,7 @@
 		if off+int(size) > bodyEnd {
 			return SVCBResource{}, errResourceLen
 		}
+		previousKey = key
 		totalValueLen += size
 		off += int(size)
 		n++
diff --git a/dns/dnsmessage/svcb_test.go b/dns/dnsmessage/svcb_test.go
index 74fcccd..64a1bfd 100644
--- a/dns/dnsmessage/svcb_test.go
+++ b/dns/dnsmessage/svcb_test.go
@@ -366,28 +366,74 @@
 	testRecord(bytes, parsed)
 }
 
-func TestSVCBPackLongValue(t *testing.T) {
-	b := NewBuilder(nil, Header{})
-	b.StartQuestions()
-	b.StartAnswers()
-
-	res := SVCBResource{
-		Target: MustNewName("example.com."),
-		Params: []SVCParam{
-			{
-				Key:   SVCParamMandatory,
-				Value: make([]byte, math.MaxUint16+1),
+func TestSVCBPackErrors(t *testing.T) {
+	for _, test := range []struct {
+		name string
+		r    SVCBResource
+	}{
+		{
+			name: "long value",
+			r: SVCBResource{
+				Target: MustNewName("example.com."),
+				Params: []SVCParam{
+					{
+						Key:   SVCParamMandatory,
+						Value: make([]byte, math.MaxUint16+1),
+					},
+				},
 			},
 		},
-	}
+		{
+			name: "out-of-order keys",
+			r: SVCBResource{
+				Target: MustNewName("example.com."),
+				Params: []SVCParam{
+					{
+						Key:   SVCParamPort, // 3
+						Value: []byte("443"),
+					},
+					{
+						Key:   SVCParamALPN, // 1
+						Value: []byte("h2"),
+					},
+				},
+			},
+		},
+		{
+			name: "duplicate keys",
+			r: SVCBResource{
+				Target: MustNewName("example.com."),
+				Params: []SVCParam{
+					{
+						Key:   SVCParamALPN,
+						Value: []byte("h3"),
+					},
+					{
+						Key:   SVCParamALPN,
+						Value: []byte("h2"),
+					},
+				},
+			},
+		},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			b := NewBuilder(nil, Header{})
+			b.StartQuestions()
+			b.StartAnswers()
+			if err := b.SVCBResource(ResourceHeader{
+				Name: MustNewName("example.com."),
+			}, test.r); err == nil {
+				t.Errorf("b.SVCBResource() succeeded; want error")
+			}
 
-	err := b.SVCBResource(ResourceHeader{Name: MustNewName("example.com.")}, res)
-	if err == nil || err.Error() != "ResourceBody: SVCBResource.Params: value too long (>65535 bytes)" {
-		t.Fatalf(`b.SVCBResource() = %v; want = "ResourceBody: SVCBResource.Params: value too long (>65535 bytes)"`, err)
-	}
-
-	err = b.HTTPSResource(ResourceHeader{Name: MustNewName("example.com.")}, HTTPSResource{res})
-	if err == nil || err.Error() != "ResourceBody: SVCBResource.Params: value too long (>65535 bytes)" {
-		t.Fatalf(`b.HTTPSResource() = %v; want = "ResourceBody: SVCBResource.Params: value too long (>65535 bytes)"`, err)
+			b = NewBuilder(nil, Header{})
+			b.StartQuestions()
+			b.StartAnswers()
+			if err := b.HTTPSResource(ResourceHeader{
+				Name: MustNewName("example.com."),
+			}, HTTPSResource{test.r}); err == nil {
+				t.Errorf("b.HTTPSResource() succeeded; want error")
+			}
+		})
 	}
 }