]> Cypherpunks repositories - gostls13.git/commitdiff
net/netip: add a fuzz test
authorAndrew LeFevre <capnspacehook@gmail.com>
Thu, 16 Dec 2021 05:25:09 +0000 (05:25 +0000)
committerEmmanuel Odeke <emmanuel@orijtech.com>
Mon, 20 Dec 2021 23:46:23 +0000 (23:46 +0000)
This is a pretty straight port of the fuzz test at https://github.com/inetaf/netaddr.

The MarshalText methods of netip.Addr and net.IP, the Is* methods of netip.Addr
and net.IP and the MarshalText and String methods of netip.Addr are also
checked to ensure that they behave the same way.

Fixes #49367

Change-Id: I44abb01f2a7af45f39597992a1fc7ff0305728fa
GitHub-Last-Rev: c2323b0ae119b804a58b810ad7f0e2bb1b3a38ac
GitHub-Pull-Request: golang/go#50108
Reviewed-on: https://go-review.googlesource.com/c/go/+/371055
Trust: Matt Layher <mdlayher@gmail.com>
Trust: Katie Hockman <katie@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
src/net/netip/fuzz_test.go [new file with mode: 0644]

diff --git a/src/net/netip/fuzz_test.go b/src/net/netip/fuzz_test.go
new file mode 100644 (file)
index 0000000..c9fc6c9
--- /dev/null
@@ -0,0 +1,351 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package netip_test
+
+import (
+       "bytes"
+       "encoding"
+       "fmt"
+       "net"
+       . "net/netip"
+       "reflect"
+       "strings"
+       "testing"
+)
+
+var corpus = []string{
+       // Basic zero IPv4 address.
+       "0.0.0.0",
+       // Basic non-zero IPv4 address.
+       "192.168.140.255",
+       // IPv4 address in windows-style "print all the digits" form.
+       "010.000.015.001",
+       // IPv4 address with a silly amount of leading zeros.
+       "000001.00000002.00000003.000000004",
+       // 4-in-6 with octet with leading zero
+       "::ffff:1.2.03.4",
+       // Basic zero IPv6 address.
+       "::",
+       // Localhost IPv6.
+       "::1",
+       // Fully expanded IPv6 address.
+       "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b",
+       // IPv6 with elided fields in the middle.
+       "fd7a:115c::626b:430b",
+       // IPv6 with elided fields at the end.
+       "fd7a:115c:a1e0:ab12:4843:cd96::",
+       // IPv6 with single elided field at the end.
+       "fd7a:115c:a1e0:ab12:4843:cd96:626b::",
+       "fd7a:115c:a1e0:ab12:4843:cd96:626b:0",
+       // IPv6 with single elided field in the middle.
+       "fd7a:115c:a1e0::4843:cd96:626b:430b",
+       "fd7a:115c:a1e0:0:4843:cd96:626b:430b",
+       // IPv6 with the trailing 32 bits written as IPv4 dotted decimal. (4in6)
+       "::ffff:192.168.140.255",
+       "::ffff:192.168.140.255",
+       // IPv6 with a zone specifier.
+       "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b%eth0",
+       // IPv6 with dotted decimal and zone specifier.
+       "1:2::ffff:192.168.140.255%eth1",
+       "1:2::ffff:c0a8:8cff%eth1",
+       // IPv6 with capital letters.
+       "FD9E:1A04:F01D::1",
+       "fd9e:1a04:f01d::1",
+       // Empty string.
+       "",
+       // Garbage non-IP.
+       "bad",
+       // Single number. Some parsers accept this as an IPv4 address in
+       // big-endian uint32 form, but we don't.
+       "1234",
+       // IPv4 with a zone specifier.
+       "1.2.3.4%eth0",
+       // IPv4 field must have at least one digit.
+       ".1.2.3",
+       "1.2.3.",
+       "1..2.3",
+       // IPv4 address too long.
+       "1.2.3.4.5",
+       // IPv4 in dotted octal form.
+       "0300.0250.0214.0377",
+       // IPv4 in dotted hex form.
+       "0xc0.0xa8.0x8c.0xff",
+       // IPv4 in class B form.
+       "192.168.12345",
+       // IPv4 in class B form, with a small enough number to be
+       // parseable as a regular dotted decimal field.
+       "127.0.1",
+       // IPv4 in class A form.
+       "192.1234567",
+       // IPv4 in class A form, with a small enough number to be
+       // parseable as a regular dotted decimal field.
+       "127.1",
+       // IPv4 field has value >255.
+       "192.168.300.1",
+       // IPv4 with too many fields.
+       "192.168.0.1.5.6",
+       // IPv6 with not enough fields.
+       "1:2:3:4:5:6:7",
+       // IPv6 with too many fields.
+       "1:2:3:4:5:6:7:8:9",
+       // IPv6 with 8 fields and a :: expander.
+       "1:2:3:4::5:6:7:8",
+       // IPv6 with a field bigger than 2b.
+       "fe801::1",
+       // IPv6 with non-hex values in field.
+       "fe80:tail:scal:e::",
+       // IPv6 with a zone delimiter but no zone.
+       "fe80::1%",
+       // IPv6 with a zone specifier of zero.
+       "::ffff:0:0%0",
+       // IPv6 (without ellipsis) with too many fields for trailing embedded IPv4.
+       "ffff:ffff:ffff:ffff:ffff:ffff:ffff:192.168.140.255",
+       // IPv6 (with ellipsis) with too many fields for trailing embedded IPv4.
+       "ffff::ffff:ffff:ffff:ffff:ffff:ffff:192.168.140.255",
+       // IPv6 with invalid embedded IPv4.
+       "::ffff:192.168.140.bad",
+       // IPv6 with multiple ellipsis ::.
+       "fe80::1::1",
+       // IPv6 with invalid non hex/colon character.
+       "fe80:1?:1",
+       // IPv6 with truncated bytes after single colon.
+       "fe80:",
+       // AddrPort strings.
+       "1.2.3.4:51820",
+       "[fd7a:115c:a1e0:ab12:4843:cd96:626b:430b]:80",
+       "[::ffff:c000:0280]:65535",
+       "[::ffff:c000:0280%eth0]:1",
+       // Prefix strings.
+       "1.2.3.4/24",
+       "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118",
+       "::ffff:c000:0280/96",
+       "::ffff:c000:0280%eth0/37",
+}
+
+func FuzzParse(f *testing.F) {
+       for _, seed := range corpus {
+               f.Add(seed)
+       }
+
+       f.Fuzz(func(t *testing.T, s string) {
+               ip, _ := ParseAddr(s)
+               checkStringParseRoundTrip(t, ip, ParseAddr)
+               checkEncoding(t, ip)
+
+               // Check that we match the net's IP parser, modulo zones.
+               if !strings.Contains(s, "%") {
+                       stdip := net.ParseIP(s)
+                       if !ip.IsValid() != (stdip == nil) {
+                               t.Errorf("ParseAddr zero != net.ParseIP nil: ip=%q stdip=%q", ip, stdip)
+                       }
+
+                       if ip.IsValid() && !ip.Is4In6() {
+                               buf, err := ip.MarshalText()
+                               if err != nil {
+                                       t.Fatal(err)
+                               }
+                               buf2, err := stdip.MarshalText()
+                               if err != nil {
+                                       t.Fatal(err)
+                               }
+                               if !bytes.Equal(buf, buf2) {
+                                       t.Errorf("Addr.MarshalText() != net.IP.MarshalText(): ip=%q stdip=%q", ip, stdip)
+                               }
+                               if ip.String() != stdip.String() {
+                                       t.Errorf("Addr.String() != net.IP.String(): ip=%q stdip=%q", ip, stdip)
+                               }
+                               if ip.IsGlobalUnicast() != stdip.IsGlobalUnicast() {
+                                       t.Errorf("Addr.IsGlobalUnicast() != net.IP.IsGlobalUnicast(): ip=%q stdip=%q", ip, stdip)
+                               }
+                               if ip.IsInterfaceLocalMulticast() != stdip.IsInterfaceLocalMulticast() {
+                                       t.Errorf("Addr.IsInterfaceLocalMulticast() != net.IP.IsInterfaceLocalMulticast(): ip=%q stdip=%q", ip, stdip)
+                               }
+                               if ip.IsLinkLocalMulticast() != stdip.IsLinkLocalMulticast() {
+                                       t.Errorf("Addr.IsLinkLocalMulticast() != net.IP.IsLinkLocalMulticast(): ip=%q stdip=%q", ip, stdip)
+                               }
+                               if ip.IsLinkLocalUnicast() != stdip.IsLinkLocalUnicast() {
+                                       t.Errorf("Addr.IsLinkLocalUnicast() != net.IP.IsLinkLocalUnicast(): ip=%q stdip=%q", ip, stdip)
+                               }
+                               if ip.IsLoopback() != stdip.IsLoopback() {
+                                       t.Errorf("Addr.IsLoopback() != net.IP.IsLoopback(): ip=%q stdip=%q", ip, stdip)
+                               }
+                               if ip.IsMulticast() != stdip.IsMulticast() {
+                                       t.Errorf("Addr.IsMulticast() != net.IP.IsMulticast(): ip=%q stdip=%q", ip, stdip)
+                               }
+                               if ip.IsPrivate() != stdip.IsPrivate() {
+                                       t.Errorf("Addr.IsPrivate() != net.IP.IsPrivate(): ip=%q stdip=%q", ip, stdip)
+                               }
+                               if ip.IsUnspecified() != stdip.IsUnspecified() {
+                                       t.Errorf("Addr.IsUnspecified() != net.IP.IsUnspecified(): ip=%q stdip=%q", ip, stdip)
+                               }
+                       }
+               }
+
+               // Check that .Next().Prev() and .Prev().Next() preserve the IP.
+               if ip.IsValid() && ip.Next().IsValid() && ip.Next().Prev() != ip {
+                       t.Errorf(".Next.Prev did not round trip: ip=%q .next=%q .next.prev=%q", ip, ip.Next(), ip.Next().Prev())
+               }
+               if ip.IsValid() && ip.Prev().IsValid() && ip.Prev().Next() != ip {
+                       t.Errorf(".Prev.Next did not round trip: ip=%q .prev=%q .prev.next=%q", ip, ip.Prev(), ip.Prev().Next())
+               }
+
+               port, err := ParseAddrPort(s)
+               if err == nil {
+                       checkStringParseRoundTrip(t, port, ParseAddrPort)
+                       checkEncoding(t, port)
+               }
+               port = AddrPortFrom(ip, 80)
+               checkStringParseRoundTrip(t, port, ParseAddrPort)
+               checkEncoding(t, port)
+
+               ipp, err := ParsePrefix(s)
+               if err == nil {
+                       checkStringParseRoundTrip(t, ipp, ParsePrefix)
+                       checkEncoding(t, ipp)
+               }
+               ipp = PrefixFrom(ip, 8)
+               checkStringParseRoundTrip(t, ipp, ParsePrefix)
+               checkEncoding(t, ipp)
+       })
+}
+
+// checkTextMarshaler checks that x's MarshalText and UnmarshalText functions round trip correctly.
+func checkTextMarshaler(t *testing.T, x encoding.TextMarshaler) {
+       buf, err := x.MarshalText()
+       if err != nil {
+               t.Fatal(err)
+       }
+       y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.TextUnmarshaler)
+       err = y.UnmarshalText(buf)
+       if err != nil {
+               t.Logf("(%v).MarshalText() = %q", x, buf)
+               t.Fatalf("(%T).UnmarshalText(%q) = %v", y, buf, err)
+       }
+       e := reflect.ValueOf(y).Elem().Interface()
+       if !reflect.DeepEqual(x, e) {
+               t.Logf("(%v).MarshalText() = %q", x, buf)
+               t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y)
+               t.Fatalf("MarshalText/UnmarshalText failed to round trip: %#v != %#v", x, e)
+       }
+       buf2, err := y.(encoding.TextMarshaler).MarshalText()
+       if err != nil {
+               t.Logf("(%v).MarshalText() = %q", x, buf)
+               t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y)
+               t.Fatalf("failed to MarshalText a second time: %v", err)
+       }
+       if !bytes.Equal(buf, buf2) {
+               t.Logf("(%v).MarshalText() = %q", x, buf)
+               t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y)
+               t.Logf("(%v).MarshalText() = %q", y, buf2)
+               t.Fatalf("second MarshalText differs from first: %q != %q", buf, buf2)
+       }
+}
+
+// checkBinaryMarshaler checks that x's MarshalText and UnmarshalText functions round trip correctly.
+func checkBinaryMarshaler(t *testing.T, x encoding.BinaryMarshaler) {
+       buf, err := x.MarshalBinary()
+       if err != nil {
+               t.Fatal(err)
+       }
+       y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.BinaryUnmarshaler)
+       err = y.UnmarshalBinary(buf)
+       if err != nil {
+               t.Logf("(%v).MarshalBinary() = %q", x, buf)
+               t.Fatalf("(%T).UnmarshalBinary(%q) = %v", y, buf, err)
+       }
+       e := reflect.ValueOf(y).Elem().Interface()
+       if !reflect.DeepEqual(x, e) {
+               t.Logf("(%v).MarshalBinary() = %q", x, buf)
+               t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y)
+               t.Fatalf("MarshalBinary/UnmarshalBinary failed to round trip: %#v != %#v", x, e)
+       }
+       buf2, err := y.(encoding.BinaryMarshaler).MarshalBinary()
+       if err != nil {
+               t.Logf("(%v).MarshalBinary() = %q", x, buf)
+               t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y)
+               t.Fatalf("failed to MarshalBinary a second time: %v", err)
+       }
+       if !bytes.Equal(buf, buf2) {
+               t.Logf("(%v).MarshalBinary() = %q", x, buf)
+               t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y)
+               t.Logf("(%v).MarshalBinary() = %q", y, buf2)
+               t.Fatalf("second MarshalBinary differs from first: %q != %q", buf, buf2)
+       }
+}
+
+func checkTextMarshalMatchesString(t *testing.T, x netipType) {
+       buf, err := x.MarshalText()
+       if err != nil {
+               t.Fatal(err)
+       }
+       str := x.String()
+       if string(buf) != str {
+               t.Fatalf("%v: MarshalText = %q, String = %q", x, buf, str)
+       }
+}
+
+type appendMarshaler interface {
+       encoding.TextMarshaler
+       AppendTo([]byte) []byte
+}
+
+// checkTextMarshalMatchesAppendTo checks that x's MarshalText matches x's AppendTo.
+func checkTextMarshalMatchesAppendTo(t *testing.T, x appendMarshaler) {
+       buf, err := x.MarshalText()
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       buf2 := make([]byte, 0, len(buf))
+       buf2 = x.AppendTo(buf2)
+       if !bytes.Equal(buf, buf2) {
+               t.Fatalf("%v: MarshalText = %q, AppendTo = %q", x, buf, buf2)
+       }
+}
+
+type netipType interface {
+       encoding.BinaryMarshaler
+       encoding.TextMarshaler
+       fmt.Stringer
+       IsValid() bool
+}
+
+type netipTypeCmp interface {
+       comparable
+       netipType
+}
+
+// checkStringParseRoundTrip checks that x's String method and the provided parse function can round trip correctly.
+func checkStringParseRoundTrip[P netipTypeCmp](t *testing.T, x P, parse func(string) (P, error)) {
+       if !x.IsValid() {
+               // Ignore invalid values.
+               return
+       }
+
+       s := x.String()
+       y, err := parse(s)
+       if err != nil {
+               t.Fatalf("s=%q err=%v", s, err)
+       }
+       if x != y {
+               t.Fatalf("%T round trip identity failure: s=%q x=%#v y=%#v", x, s, x, y)
+       }
+       s2 := y.String()
+       if s != s2 {
+               t.Fatalf("%T String round trip identity failure: s=%#v s2=%#v", x, s, s2)
+       }
+}
+
+func checkEncoding(t *testing.T, x netipType) {
+       if x.IsValid() {
+               checkTextMarshaler(t, x)
+               checkBinaryMarshaler(t, x)
+               checkTextMarshalMatchesString(t, x)
+       }
+
+       if am, ok := x.(appendMarshaler); ok {
+               checkTextMarshalMatchesAppendTo(t, am)
+       }
+}