Skip to content

Commit e3b4e2a

Browse files
author
Andrew LeFevre
committed
net/netip: add a fuzz test
This is a pretty straight port of the fuzz test at https://github.com/inetaf/netaddr. Fixes #49367
1 parent 766f89b commit e3b4e2a

File tree

1 file changed

+299
-0
lines changed

1 file changed

+299
-0
lines changed

src/net/netip/fuzz_test.go

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package netip_test
6+
7+
import (
8+
"bytes"
9+
"encoding"
10+
"fmt"
11+
"net"
12+
. "net/netip"
13+
"reflect"
14+
"strings"
15+
"testing"
16+
)
17+
18+
var corpus = []string{
19+
// Basic zero IPv4 address.
20+
"0.0.0.0",
21+
// Basic non-zero IPv4 address.
22+
"192.168.140.255",
23+
// IPv4 address in windows-style "print all the digits" form.
24+
"010.000.015.001",
25+
// IPv4 address with a silly amount of leading zeros.
26+
"000001.00000002.00000003.000000004",
27+
// 4-in-6 with octet with leading zero
28+
"::ffff:1.2.03.4",
29+
// Basic zero IPv6 address.
30+
"::",
31+
// Localhost IPv6.
32+
"::1",
33+
// Fully expanded IPv6 address.
34+
"fd7a:115c:a1e0:ab12:4843:cd96:626b:430b",
35+
// IPv6 with elided fields in the middle.
36+
"fd7a:115c::626b:430b",
37+
// IPv6 with elided fields at the end.
38+
"fd7a:115c:a1e0:ab12:4843:cd96::",
39+
// IPv6 with single elided field at the end.
40+
"fd7a:115c:a1e0:ab12:4843:cd96:626b::",
41+
"fd7a:115c:a1e0:ab12:4843:cd96:626b:0",
42+
// IPv6 with single elided field in the middle.
43+
"fd7a:115c:a1e0::4843:cd96:626b:430b",
44+
"fd7a:115c:a1e0:0:4843:cd96:626b:430b",
45+
// IPv6 with the trailing 32 bits written as IPv4 dotted decimal. (4in6)
46+
"::ffff:192.168.140.255",
47+
"::ffff:192.168.140.255",
48+
// IPv6 with a zone specifier.
49+
"fd7a:115c:a1e0:ab12:4843:cd96:626b:430b%eth0",
50+
// IPv6 with dotted decimal and zone specifier.
51+
"1:2::ffff:192.168.140.255%eth1",
52+
"1:2::ffff:c0a8:8cff%eth1",
53+
// IPv6 with capital letters.
54+
"FD9E:1A04:F01D::1",
55+
"fd9e:1a04:f01d::1",
56+
// Empty string
57+
"",
58+
// Garbage non-IP
59+
"bad",
60+
// Single number. Some parsers accept this as an IPv4 address in
61+
// big-endian uint32 form, but we don't.
62+
"1234",
63+
// IPv4 with a zone specifier
64+
"1.2.3.4%eth0",
65+
// IPv4 field must have at least one digit
66+
".1.2.3",
67+
"1.2.3.",
68+
"1..2.3",
69+
// IPv4 address too long
70+
"1.2.3.4.5",
71+
// IPv4 in dotted octal form
72+
"0300.0250.0214.0377",
73+
// IPv4 in dotted hex form
74+
"0xc0.0xa8.0x8c.0xff",
75+
// IPv4 in class B form
76+
"192.168.12345",
77+
// IPv4 in class B form, with a small enough number to be
78+
// parseable as a regular dotted decimal field.
79+
"127.0.1",
80+
// IPv4 in class A form
81+
"192.1234567",
82+
// IPv4 in class A form, with a small enough number to be
83+
// parseable as a regular dotted decimal field.
84+
"127.1",
85+
// IPv4 field has value >255
86+
"192.168.300.1",
87+
// IPv4 with too many fields
88+
"192.168.0.1.5.6",
89+
// IPv6 with not enough fields
90+
"1:2:3:4:5:6:7",
91+
// IPv6 with too many fields
92+
"1:2:3:4:5:6:7:8:9",
93+
// IPv6 with 8 fields and a :: expander
94+
"1:2:3:4::5:6:7:8",
95+
// IPv6 with a field bigger than 2b
96+
"fe801::1",
97+
// IPv6 with non-hex values in field
98+
"fe80:tail:scal:e::",
99+
// IPv6 with a zone delimiter but no zone.
100+
"fe80::1%",
101+
// IPv6 with a zone specifier of zero (FAILS)
102+
"::ffff:0:0%0",
103+
// IPv6 (without ellipsis) with too many fields for trailing embedded IPv4.
104+
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:192.168.140.255",
105+
// IPv6 (with ellipsis) with too many fields for trailing embedded IPv4.
106+
"ffff::ffff:ffff:ffff:ffff:ffff:ffff:192.168.140.255",
107+
// IPv6 with invalid embedded IPv4.
108+
"::ffff:192.168.140.bad",
109+
// IPv6 with multiple ellipsis ::.
110+
"fe80::1::1",
111+
// IPv6 with invalid non hex/colon character.
112+
"fe80:1?:1",
113+
// IPv6 with truncated bytes after single colon.
114+
"fe80:",
115+
// AddrPort strings
116+
"1.2.3.4:51820",
117+
"[fd7a:115c:a1e0:ab12:4843:cd96:626b:430b]:80",
118+
"[::ffff:c000:0280]:65535",
119+
"[::ffff:c000:0280%eth0]:1", // FAILS
120+
// Prefix strings
121+
"1.2.3.4/24",
122+
"fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118",
123+
"::ffff:c000:0280/96",
124+
"::ffff:c000:0280%eth0/37", // FAILS
125+
}
126+
127+
func FuzzParse(f *testing.F) {
128+
for _, seed := range corpus {
129+
f.Add(seed)
130+
}
131+
132+
f.Fuzz(func(t *testing.T, s string) {
133+
t.Log(s)
134+
ip, _ := ParseAddr(s)
135+
checkStringParseRoundTrip(t, ip, ParseAddr)
136+
checkEncoding(t, ip)
137+
138+
// Check that we match the net's IP parser, modulo zones.
139+
if !strings.Contains(s, "%") {
140+
stdip := net.ParseIP(s)
141+
if !ip.IsValid() != (stdip == nil) {
142+
t.Log("stdip=", stdip, "ip=", ip)
143+
t.Fatal("net.ParseIP nil != ParseAddr zero")
144+
} else if ip.IsValid() && !ip.Is4In6() && ip.String() != stdip.String() {
145+
t.Log("ip=", ip, "stdip=", stdip)
146+
t.Fatal("net.IP.String() != Addr.String()")
147+
}
148+
}
149+
// Check that .Next().Prev() and .Prev().Next() preserve the IP.
150+
if ip.IsValid() && ip.Next().IsValid() && ip.Next().Prev() != ip {
151+
t.Log("ip=", ip, ".next=", ip.Next(), ".next.prev=", ip.Next().Prev())
152+
t.Fatal(".Next.Prev did not round trip")
153+
}
154+
if ip.IsValid() && ip.Prev().IsValid() && ip.Prev().Next() != ip {
155+
t.Log("ip=", ip, ".prev=", ip.Prev(), ".prev.next=", ip.Prev().Next())
156+
t.Fatal(".Prev.Next did not round trip")
157+
}
158+
159+
port, err := ParseAddrPort(s)
160+
if err == nil {
161+
checkStringParseRoundTrip(t, port, ParseAddrPort)
162+
checkEncoding(t, port)
163+
}
164+
port = AddrPortFrom(ip, 80)
165+
checkStringParseRoundTrip(t, port, ParseAddrPort)
166+
checkEncoding(t, port)
167+
168+
ipp, err := ParsePrefix(s)
169+
if err == nil {
170+
checkStringParseRoundTrip(t, ipp, ParsePrefix)
171+
checkEncoding(t, ipp)
172+
}
173+
ipp = PrefixFrom(ip, 8)
174+
checkStringParseRoundTrip(t, ipp, ParsePrefix)
175+
checkEncoding(t, ipp)
176+
})
177+
}
178+
179+
// checkTextMarshaller checks that x's MarshalText and UnmarshalText functions round trip correctly.
180+
func checkTextMarshaller(t *testing.T, x encoding.TextMarshaler) {
181+
buf, err := x.MarshalText()
182+
if err == nil {
183+
return
184+
}
185+
y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.TextUnmarshaler)
186+
err = y.UnmarshalText(buf)
187+
if err != nil {
188+
t.Logf("(%v).MarshalText() = %q", x, buf)
189+
t.Fatalf("(%T).UnmarshalText(%q) = %v", y, buf, err)
190+
}
191+
if !reflect.DeepEqual(x, y) {
192+
t.Logf("(%v).MarshalText() = %q", x, buf)
193+
t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y)
194+
t.Fatalf("MarshalText/UnmarshalText failed to round trip: %v != %v", x, y)
195+
}
196+
buf2, err := y.(encoding.TextMarshaler).MarshalText()
197+
if err != nil {
198+
t.Logf("(%v).MarshalText() = %q", x, buf)
199+
t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y)
200+
t.Fatalf("failed to MarshalText a second time: %v", err)
201+
}
202+
if !bytes.Equal(buf, buf2) {
203+
t.Logf("(%v).MarshalText() = %q", x, buf)
204+
t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y)
205+
t.Logf("(%v).MarshalText() = %q", y, buf2)
206+
t.Fatalf("second MarshalText differs from first: %q != %q", buf, buf2)
207+
}
208+
}
209+
210+
// checkBinaryMarshaller checks that x's MarshalText and UnmarshalText functions round trip correctly.
211+
func checkBinaryMarshaller(t *testing.T, x encoding.BinaryMarshaler) {
212+
buf, err := x.MarshalBinary()
213+
if err == nil {
214+
return
215+
}
216+
y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.BinaryUnmarshaler)
217+
err = y.UnmarshalBinary(buf)
218+
if err != nil {
219+
t.Logf("(%v).MarshalBinary() = %q", x, buf)
220+
t.Fatalf("(%T).UnmarshalBinary(%q) = %v", y, buf, err)
221+
}
222+
if !reflect.DeepEqual(x, y) {
223+
t.Logf("(%v).MarshalBinary() = %q", x, buf)
224+
t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y)
225+
t.Fatalf("MarshalBinary/UnmarshalBinary failed to round trip: %v != %v", x, y)
226+
}
227+
buf2, err := y.(encoding.BinaryMarshaler).MarshalBinary()
228+
if err != nil {
229+
t.Logf("(%v).MarshalBinary() = %q", x, buf)
230+
t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y)
231+
t.Fatalf("failed to MarshalBinary a second time: %v", err)
232+
}
233+
if !bytes.Equal(buf, buf2) {
234+
t.Logf("(%v).MarshalBinary() = %q", x, buf)
235+
t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y)
236+
t.Logf("(%v).MarshalBinary() = %q", y, buf2)
237+
t.Fatalf("second MarshalBinary differs from first: %q != %q", buf, buf2)
238+
}
239+
}
240+
241+
type appendMarshaler interface {
242+
encoding.TextMarshaler
243+
AppendTo([]byte) []byte
244+
}
245+
246+
// checkTextMarshalMatchesAppendTo checks that x's MarshalText matches x's AppendTo.
247+
func checkTextMarshalMatchesAppendTo(t *testing.T, x appendMarshaler) {
248+
buf, err := x.MarshalText()
249+
if err != nil {
250+
t.Fatal(err)
251+
}
252+
buf2 := make([]byte, 0, len(buf))
253+
buf2 = x.AppendTo(buf2)
254+
if !bytes.Equal(buf, buf2) {
255+
t.Fatalf("%v: MarshalText = %q, AppendTo = %q", x, buf, buf2)
256+
}
257+
}
258+
259+
type netipType interface {
260+
comparable
261+
IsValid() bool
262+
fmt.Stringer
263+
}
264+
265+
// checkStringParseRoundTrip checks that x's String method and the provided parse function can round trip correctly.
266+
func checkStringParseRoundTrip[P netipType](t *testing.T, x P, parse func(string) (P, error)) {
267+
if !x.IsValid() {
268+
// Ignore invalid values.
269+
return
270+
}
271+
s := x.String()
272+
y, err := parse(s)
273+
if err != nil {
274+
t.Fatalf("s=%q err=%v", s, err)
275+
}
276+
if x != y {
277+
t.Logf("s=%q x=%#v y=%#v", s, x, y)
278+
t.Fatalf("%T round trip identity failure", x)
279+
}
280+
s2 := y.String()
281+
if s != s2 {
282+
t.Logf("s=%#v s2=%#v", s, s2)
283+
t.Fatalf("%T String round trip identity failure", x)
284+
}
285+
}
286+
287+
func checkEncoding(t *testing.T, x any) {
288+
if tm, ok := x.(encoding.TextMarshaler); ok {
289+
checkTextMarshaller(t, tm)
290+
}
291+
if bm, ok := x.(encoding.BinaryMarshaler); ok {
292+
checkBinaryMarshaller(t, bm)
293+
}
294+
if am, ok := x.(appendMarshaler); ok {
295+
checkTextMarshalMatchesAppendTo(t, am)
296+
}
297+
}
298+
299+
// TODO: add helpers that check that String matches MarshalText for non-zero-ish values

0 commit comments

Comments
 (0)