376 lines
14 KiB
Diff
376 lines
14 KiB
Diff
From 31d60cda1f58b7558fc5725d2b9e4531655d980e Mon Sep 17 00:00:00 2001
|
|
From: Roland Shoemaker <roland@golang.org>
|
|
Date: Thu, 27 May 2021 10:40:06 -0700
|
|
Subject: [PATCH] [release-branch.go1.15] net: verify results from Lookup* are
|
|
valid domain names
|
|
|
|
For the methods LookupCNAME, LookupSRV, LookupMX, LookupNS, and
|
|
LookupAddr check that the returned domain names are in fact valid DNS
|
|
names using the existing isDomainName function.
|
|
|
|
Thanks to Philipp Jeitner and Haya Shulman from Fraunhofer SIT for
|
|
reporting this issue.
|
|
|
|
Updates #46241
|
|
Fixes #46356
|
|
Fixes CVE-2021-33195
|
|
|
|
Conflict:NA
|
|
Reference:https://github.com/golang/go/commit/31d60cda1f58b7558fc5725d2b9e4531655d980e
|
|
|
|
Change-Id: I47a4f58c031cb752f732e88bbdae7f819f0af4f3
|
|
Reviewed-on: https://go-review.googlesource.com/c/go/+/323131
|
|
Trust: Roland Shoemaker <roland@golang.org>
|
|
Run-TryBot: Roland Shoemaker <roland@golang.org>
|
|
TryBot-Result: Go Bot <gobot@golang.org>
|
|
Reviewed-by: Filippo Valsorda <filippo@golang.org>
|
|
Reviewed-by: Katie Hockman <katie@golang.org>
|
|
(cherry picked from commit cdcd02842da7c004efd023881e3719105209c908)
|
|
Reviewed-on: https://go-review.googlesource.com/c/go/+/323269
|
|
---
|
|
src/net/dnsclient_unix_test.go | 157 +++++++++++++++++++++++++++++++++
|
|
src/net/lookup.go | 111 ++++++++++++++++++++---
|
|
2 files changed, 255 insertions(+), 13 deletions(-)
|
|
|
|
diff --git a/src/net/dnsclient_unix_test.go b/src/net/dnsclient_unix_test.go
|
|
index 06553636ee..819f20b887 100644
|
|
--- a/src/net/dnsclient_unix_test.go
|
|
+++ b/src/net/dnsclient_unix_test.go
|
|
@@ -1799,3 +1799,160 @@ func TestPTRandNonPTR(t *testing.T) {
|
|
t.Errorf("names = %q; want %q", names, want)
|
|
}
|
|
}
|
|
+
|
|
+func TestCVE202133195(t *testing.T) {
|
|
+ fake := fakeDNSServer{
|
|
+ rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
|
|
+ r := dnsmessage.Message{
|
|
+ Header: dnsmessage.Header{
|
|
+ ID: q.Header.ID,
|
|
+ Response: true,
|
|
+ RCode: dnsmessage.RCodeSuccess,
|
|
+ RecursionAvailable: true,
|
|
+ },
|
|
+ Questions: q.Questions,
|
|
+ }
|
|
+ switch q.Questions[0].Type {
|
|
+ case dnsmessage.TypeCNAME:
|
|
+ r.Answers = []dnsmessage.Resource{}
|
|
+ case dnsmessage.TypeA: // CNAME lookup uses a A/AAAA as a proxy
|
|
+ r.Answers = append(r.Answers,
|
|
+ dnsmessage.Resource{
|
|
+ Header: dnsmessage.ResourceHeader{
|
|
+ Name: dnsmessage.MustNewName("<html>.golang.org."),
|
|
+ Type: dnsmessage.TypeA,
|
|
+ Class: dnsmessage.ClassINET,
|
|
+ Length: 4,
|
|
+ },
|
|
+ Body: &dnsmessage.AResource{
|
|
+ A: TestAddr,
|
|
+ },
|
|
+ },
|
|
+ )
|
|
+ case dnsmessage.TypeSRV:
|
|
+ n := q.Questions[0].Name
|
|
+ if n.String() == "_hdr._tcp.golang.org." {
|
|
+ n = dnsmessage.MustNewName("<html>.golang.org.")
|
|
+ }
|
|
+ r.Answers = append(r.Answers,
|
|
+ dnsmessage.Resource{
|
|
+ Header: dnsmessage.ResourceHeader{
|
|
+ Name: n,
|
|
+ Type: dnsmessage.TypeSRV,
|
|
+ Class: dnsmessage.ClassINET,
|
|
+ Length: 4,
|
|
+ },
|
|
+ Body: &dnsmessage.SRVResource{
|
|
+ Target: dnsmessage.MustNewName("<html>.golang.org."),
|
|
+ },
|
|
+ },
|
|
+ )
|
|
+ case dnsmessage.TypeMX:
|
|
+ r.Answers = append(r.Answers,
|
|
+ dnsmessage.Resource{
|
|
+ Header: dnsmessage.ResourceHeader{
|
|
+ Name: dnsmessage.MustNewName("<html>.golang.org."),
|
|
+ Type: dnsmessage.TypeMX,
|
|
+ Class: dnsmessage.ClassINET,
|
|
+ Length: 4,
|
|
+ },
|
|
+ Body: &dnsmessage.MXResource{
|
|
+ MX: dnsmessage.MustNewName("<html>.golang.org."),
|
|
+ },
|
|
+ },
|
|
+ )
|
|
+ case dnsmessage.TypeNS:
|
|
+ r.Answers = append(r.Answers,
|
|
+ dnsmessage.Resource{
|
|
+ Header: dnsmessage.ResourceHeader{
|
|
+ Name: dnsmessage.MustNewName("<html>.golang.org."),
|
|
+ Type: dnsmessage.TypeNS,
|
|
+ Class: dnsmessage.ClassINET,
|
|
+ Length: 4,
|
|
+ },
|
|
+ Body: &dnsmessage.NSResource{
|
|
+ NS: dnsmessage.MustNewName("<html>.golang.org."),
|
|
+ },
|
|
+ },
|
|
+ )
|
|
+ case dnsmessage.TypePTR:
|
|
+ r.Answers = append(r.Answers,
|
|
+ dnsmessage.Resource{
|
|
+ Header: dnsmessage.ResourceHeader{
|
|
+ Name: dnsmessage.MustNewName("<html>.golang.org."),
|
|
+ Type: dnsmessage.TypePTR,
|
|
+ Class: dnsmessage.ClassINET,
|
|
+ Length: 4,
|
|
+ },
|
|
+ Body: &dnsmessage.PTRResource{
|
|
+ PTR: dnsmessage.MustNewName("<html>.golang.org."),
|
|
+ },
|
|
+ },
|
|
+ )
|
|
+ }
|
|
+ return r, nil
|
|
+ },
|
|
+ }
|
|
+
|
|
+ r := Resolver{PreferGo: true, Dial: fake.DialContext}
|
|
+ // Change the default resolver to match our manipulated resolver
|
|
+ originalDefault := DefaultResolver
|
|
+ DefaultResolver = &r
|
|
+ defer func() {
|
|
+ DefaultResolver = originalDefault
|
|
+ }()
|
|
+
|
|
+ _, err := r.LookupCNAME(context.Background(), "golang.org")
|
|
+ if expected := "lookup golang.org: CNAME target is invalid"; err == nil || err.Error() != expected {
|
|
+ t.Errorf("Resolver.LookupCNAME returned unexpected error, got %q, want %q", err.Error(), expected)
|
|
+ }
|
|
+ _, err = LookupCNAME("golang.org")
|
|
+ if expected := "lookup golang.org: CNAME target is invalid"; err == nil || err.Error() != expected {
|
|
+ t.Errorf("LookupCNAME returned unexpected error, got %q, want %q", err.Error(), expected)
|
|
+ }
|
|
+
|
|
+ _, _, err = r.LookupSRV(context.Background(), "target", "tcp", "golang.org")
|
|
+ if expected := "lookup golang.org: SRV target is invalid"; err == nil || err.Error() != expected {
|
|
+ t.Errorf("Resolver.LookupSRV returned unexpected error, got %q, want %q", err.Error(), expected)
|
|
+ }
|
|
+ _, _, err = LookupSRV("target", "tcp", "golang.org")
|
|
+ if expected := "lookup golang.org: SRV target is invalid"; err == nil || err.Error() != expected {
|
|
+ t.Errorf("LookupSRV returned unexpected error, got %q, want %q", err.Error(), expected)
|
|
+ }
|
|
+
|
|
+ _, _, err = r.LookupSRV(context.Background(), "hdr", "tcp", "golang.org")
|
|
+ if expected := "lookup golang.org: SRV header name is invalid"; err == nil || err.Error() != expected {
|
|
+ t.Errorf("Resolver.LookupSRV returned unexpected error, got %q, want %q", err.Error(), expected)
|
|
+ }
|
|
+ _, _, err = LookupSRV("hdr", "tcp", "golang.org")
|
|
+ if expected := "lookup golang.org: SRV header name is invalid"; err == nil || err.Error() != expected {
|
|
+ t.Errorf("LookupSRV returned unexpected error, got %q, want %q", err.Error(), expected)
|
|
+ }
|
|
+
|
|
+ _, err = r.LookupMX(context.Background(), "golang.org")
|
|
+ if expected := "lookup golang.org: MX target is invalid"; err == nil || err.Error() != expected {
|
|
+ t.Errorf("Resolver.LookupMX returned unexpected error, got %q, want %q", err.Error(), expected)
|
|
+ }
|
|
+ _, err = LookupMX("golang.org")
|
|
+ if expected := "lookup golang.org: MX target is invalid"; err == nil || err.Error() != expected {
|
|
+ t.Errorf("LookupMX returned unexpected error, got %q, want %q", err.Error(), expected)
|
|
+ }
|
|
+
|
|
+ _, err = r.LookupNS(context.Background(), "golang.org")
|
|
+ if expected := "lookup golang.org: NS target is invalid"; err == nil || err.Error() != expected {
|
|
+ t.Errorf("Resolver.LookupNS returned unexpected error, got %q, want %q", err.Error(), expected)
|
|
+ }
|
|
+ _, err = LookupNS("golang.org")
|
|
+ if expected := "lookup golang.org: NS target is invalid"; err == nil || err.Error() != expected {
|
|
+ t.Errorf("LookupNS returned unexpected error, got %q, want %q", err.Error(), expected)
|
|
+ }
|
|
+
|
|
+ _, err = r.LookupAddr(context.Background(), "1.2.3.4")
|
|
+ if expected := "lookup 1.2.3.4: PTR target is invalid"; err == nil || err.Error() != expected {
|
|
+ t.Errorf("Resolver.LookupAddr returned unexpected error, got %q, want %q", err.Error(), expected)
|
|
+ }
|
|
+ _, err = LookupAddr("1.2.3.4")
|
|
+ if expected := "lookup 1.2.3.4: PTR target is invalid"; err == nil || err.Error() != expected {
|
|
+ t.Errorf("LookupAddr returned unexpected error, got %q, want %q", err.Error(), expected)
|
|
+ }
|
|
+}
|
|
diff --git a/src/net/lookup.go b/src/net/lookup.go
|
|
index 5f7119872a..0660268249 100644
|
|
--- a/src/net/lookup.go
|
|
+++ b/src/net/lookup.go
|
|
@@ -389,8 +389,11 @@ func (r *Resolver) LookupPort(ctx context.Context, network, service string) (por
|
|
// LookupCNAME does not return an error if host does not
|
|
// contain DNS "CNAME" records, as long as host resolves to
|
|
// address records.
|
|
+//
|
|
+// The returned canonical name is validated to be a properly
|
|
+// formatted presentation-format domain name.
|
|
func LookupCNAME(host string) (cname string, err error) {
|
|
- return DefaultResolver.lookupCNAME(context.Background(), host)
|
|
+ return DefaultResolver.LookupCNAME(context.Background(), host)
|
|
}
|
|
|
|
// LookupCNAME returns the canonical name for the given host.
|
|
@@ -403,8 +406,18 @@ func LookupCNAME(host string) (cname string, err error) {
|
|
// LookupCNAME does not return an error if host does not
|
|
// contain DNS "CNAME" records, as long as host resolves to
|
|
// address records.
|
|
-func (r *Resolver) LookupCNAME(ctx context.Context, host string) (cname string, err error) {
|
|
- return r.lookupCNAME(ctx, host)
|
|
+//
|
|
+// The returned canonical name is validated to be a properly
|
|
+// formatted presentation-format domain name.
|
|
+func (r *Resolver) LookupCNAME(ctx context.Context, host string) (string, error) {
|
|
+ cname, err := r.lookupCNAME(ctx, host)
|
|
+ if err != nil {
|
|
+ return "", err
|
|
+ }
|
|
+ if !isDomainName(cname) {
|
|
+ return "", &DNSError{Err: "CNAME target is invalid", Name: host}
|
|
+ }
|
|
+ return cname, nil
|
|
}
|
|
|
|
// LookupSRV tries to resolve an SRV query of the given service,
|
|
@@ -416,8 +429,11 @@ func (r *Resolver) LookupCNAME(ctx context.Context, host string) (cname string,
|
|
// That is, it looks up _service._proto.name. To accommodate services
|
|
// publishing SRV records under non-standard names, if both service
|
|
// and proto are empty strings, LookupSRV looks up name directly.
|
|
+//
|
|
+// The returned service names are validated to be properly
|
|
+// formatted presentation-format domain names.
|
|
func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) {
|
|
- return DefaultResolver.lookupSRV(context.Background(), service, proto, name)
|
|
+ return DefaultResolver.LookupSRV(context.Background(), service, proto, name)
|
|
}
|
|
|
|
// LookupSRV tries to resolve an SRV query of the given service,
|
|
@@ -429,28 +445,82 @@ func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err err
|
|
// That is, it looks up _service._proto.name. To accommodate services
|
|
// publishing SRV records under non-standard names, if both service
|
|
// and proto are empty strings, LookupSRV looks up name directly.
|
|
-func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*SRV, err error) {
|
|
- return r.lookupSRV(ctx, service, proto, name)
|
|
+//
|
|
+// The returned service names are validated to be properly
|
|
+// formatted presentation-format domain names.
|
|
+func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*SRV, error) {
|
|
+ cname, addrs, err := r.lookupSRV(ctx, service, proto, name)
|
|
+ if err != nil {
|
|
+ return "", nil, err
|
|
+ }
|
|
+ if cname != "" && !isDomainName(cname) {
|
|
+ return "", nil, &DNSError{Err: "SRV header name is invalid", Name: name}
|
|
+ }
|
|
+ for _, addr := range addrs {
|
|
+ if addr == nil {
|
|
+ continue
|
|
+ }
|
|
+ if !isDomainName(addr.Target) {
|
|
+ return "", nil, &DNSError{Err: "SRV target is invalid", Name: name}
|
|
+ }
|
|
+ }
|
|
+ return cname, addrs, nil
|
|
}
|
|
|
|
// LookupMX returns the DNS MX records for the given domain name sorted by preference.
|
|
+//
|
|
+// The returned mail server names are validated to be properly
|
|
+// formatted presentation-format domain names.
|
|
func LookupMX(name string) ([]*MX, error) {
|
|
- return DefaultResolver.lookupMX(context.Background(), name)
|
|
+ return DefaultResolver.LookupMX(context.Background(), name)
|
|
}
|
|
|
|
// LookupMX returns the DNS MX records for the given domain name sorted by preference.
|
|
+//
|
|
+// The returned mail server names are validated to be properly
|
|
+// formatted presentation-format domain names.
|
|
func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*MX, error) {
|
|
- return r.lookupMX(ctx, name)
|
|
+ records, err := r.lookupMX(ctx, name)
|
|
+ if err != nil {
|
|
+ return nil, err
|
|
+ }
|
|
+ for _, mx := range records {
|
|
+ if mx == nil {
|
|
+ continue
|
|
+ }
|
|
+ if !isDomainName(mx.Host) {
|
|
+ return nil, &DNSError{Err: "MX target is invalid", Name: name}
|
|
+ }
|
|
+ }
|
|
+ return records, nil
|
|
}
|
|
|
|
// LookupNS returns the DNS NS records for the given domain name.
|
|
+//
|
|
+// The returned name server names are validated to be properly
|
|
+// formatted presentation-format domain names.
|
|
func LookupNS(name string) ([]*NS, error) {
|
|
- return DefaultResolver.lookupNS(context.Background(), name)
|
|
+ return DefaultResolver.LookupNS(context.Background(), name)
|
|
}
|
|
|
|
// LookupNS returns the DNS NS records for the given domain name.
|
|
+//
|
|
+// The returned name server names are validated to be properly
|
|
+// formatted presentation-format domain names.
|
|
func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*NS, error) {
|
|
- return r.lookupNS(ctx, name)
|
|
+ records, err := r.lookupNS(ctx, name)
|
|
+ if err != nil {
|
|
+ return nil, err
|
|
+ }
|
|
+ for _, ns := range records {
|
|
+ if ns == nil {
|
|
+ continue
|
|
+ }
|
|
+ if !isDomainName(ns.Host) {
|
|
+ return nil, &DNSError{Err: "NS target is invalid", Name: name}
|
|
+ }
|
|
+ }
|
|
+ return records, nil
|
|
}
|
|
|
|
// LookupTXT returns the DNS TXT records for the given domain name.
|
|
@@ -466,14 +536,29 @@ func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, error)
|
|
// LookupAddr performs a reverse lookup for the given address, returning a list
|
|
// of names mapping to that address.
|
|
//
|
|
+// The returned names are validated to be properly formatted presentation-format
|
|
+// domain names.
|
|
+//
|
|
// When using the host C library resolver, at most one result will be
|
|
// returned. To bypass the host resolver, use a custom Resolver.
|
|
func LookupAddr(addr string) (names []string, err error) {
|
|
- return DefaultResolver.lookupAddr(context.Background(), addr)
|
|
+ return DefaultResolver.LookupAddr(context.Background(), addr)
|
|
}
|
|
|
|
// LookupAddr performs a reverse lookup for the given address, returning a list
|
|
// of names mapping to that address.
|
|
-func (r *Resolver) LookupAddr(ctx context.Context, addr string) (names []string, err error) {
|
|
- return r.lookupAddr(ctx, addr)
|
|
+//
|
|
+// The returned names are validated to be properly formatted presentation-format
|
|
+// domain names.
|
|
+func (r *Resolver) LookupAddr(ctx context.Context, addr string) ([]string, error) {
|
|
+ names, err := r.lookupAddr(ctx, addr)
|
|
+ if err != nil {
|
|
+ return nil, err
|
|
+ }
|
|
+ for _, name := range names {
|
|
+ if !isDomainName(name) {
|
|
+ return nil, &DNSError{Err: "PTR target is invalid", Name: addr}
|
|
+ }
|
|
+ }
|
|
+ return names, nil
|
|
}
|
|
--
|
|
2.27.0
|
|
|