Skip to content

Commit 4ab3c53

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 golang#49367
1 parent 766f89b commit 4ab3c53

File tree

1 file changed

+355
-0
lines changed

1 file changed

+355
-0
lines changed

src/net/netip/fuzz_test.go

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
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.
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",
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",
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+
ip, _ := ParseAddr(s)
134+
checkStringParseRoundTrip(t, ip, ParseAddr)
135+
checkEncoding(t, ip)
136+
137+
// Check that we match the net's IP parser, modulo zones.
138+
if !strings.Contains(s, "%") {
139+
stdip := net.ParseIP(s)
140+
if !ip.IsValid() != (stdip == nil) {
141+
t.Logf("ip=%q stdip=%q", ip, stdip)
142+
t.Fatal("ParseAddr zero != net.ParseIP nil")
143+
}
144+
145+
if ip.IsValid() && !ip.Is4In6() {
146+
if ip.String() != stdip.String() {
147+
t.Logf("ip=%q stdip=%q", ip, stdip)
148+
t.Fatal("Addr.String() != net.IP.String()")
149+
}
150+
if ip.IsGlobalUnicast() != stdip.IsGlobalUnicast() {
151+
t.Logf("ip=%q stdip=%q", ip, stdip)
152+
t.Fatal("Addr.IsGlobalUnicast() != net.IP.IsGlobalUnicast()")
153+
}
154+
if ip.IsInterfaceLocalMulticast() != stdip.IsInterfaceLocalMulticast() {
155+
t.Logf("ip=%q stdip=%q", ip, stdip)
156+
t.Fatal("Addr.IsInterfaceLocalMulticast() != net.IP.IsInterfaceLocalMulticast()")
157+
}
158+
if ip.IsLinkLocalMulticast() != stdip.IsLinkLocalMulticast() {
159+
t.Logf("ip=%q stdip=%q", ip, stdip)
160+
t.Fatal("Addr.IsLinkLocalMulticast() != net.IP.IsLinkLocalMulticast()")
161+
}
162+
if ip.IsLinkLocalUnicast() != stdip.IsLinkLocalUnicast() {
163+
t.Logf("ip=%q stdip=%q", ip, stdip)
164+
t.Fatal("Addr.IsLinkLocalUnicast() != net.IP.IsLinkLocalUnicast()")
165+
}
166+
if ip.IsLoopback() != stdip.IsLoopback() {
167+
t.Logf("ip=%q stdip=%q", ip, stdip)
168+
t.Fatal("Addr.IsLoopback() != net.IP.IsLoopback()")
169+
}
170+
if ip.IsMulticast() != stdip.IsMulticast() {
171+
t.Logf("ip=%q stdip=%q", ip, stdip)
172+
t.Fatal("Addr.IsMulticast() != net.IP.IsMulticast()")
173+
}
174+
if ip.IsPrivate() != stdip.IsPrivate() {
175+
t.Logf("ip=%q stdip=%q", ip, stdip)
176+
t.Fatal("Addr.IsPrivate() != net.IP.IsPrivate()")
177+
}
178+
if ip.IsUnspecified() != stdip.IsUnspecified() {
179+
t.Logf("ip=%q stdip=%q", ip, stdip)
180+
t.Fatal("Addr.IsUnspecified() != net.IP.IsUnspecified()")
181+
}
182+
}
183+
}
184+
185+
// Check that .Next().Prev() and .Prev().Next() preserve the IP.
186+
if ip.IsValid() && ip.Next().IsValid() && ip.Next().Prev() != ip {
187+
t.Log("ip=", ip, ".next=", ip.Next(), ".next.prev=", ip.Next().Prev())
188+
t.Fatal(".Next.Prev did not round trip")
189+
}
190+
if ip.IsValid() && ip.Prev().IsValid() && ip.Prev().Next() != ip {
191+
t.Log("ip=", ip, ".prev=", ip.Prev(), ".prev.next=", ip.Prev().Next())
192+
t.Fatal(".Prev.Next did not round trip")
193+
}
194+
195+
port, err := ParseAddrPort(s)
196+
if err == nil {
197+
checkStringParseRoundTrip(t, port, ParseAddrPort)
198+
checkEncoding(t, port)
199+
}
200+
port = AddrPortFrom(ip, 80)
201+
checkStringParseRoundTrip(t, port, ParseAddrPort)
202+
checkEncoding(t, port)
203+
204+
ipp, err := ParsePrefix(s)
205+
if err == nil {
206+
checkStringParseRoundTrip(t, ipp, ParsePrefix)
207+
checkEncoding(t, ipp)
208+
}
209+
ipp = PrefixFrom(ip, 8)
210+
checkStringParseRoundTrip(t, ipp, ParsePrefix)
211+
checkEncoding(t, ipp)
212+
})
213+
}
214+
215+
// checkTextMarshaller checks that x's MarshalText and UnmarshalText functions round trip correctly.
216+
func checkTextMarshaller(t *testing.T, x encoding.TextMarshaler) {
217+
buf, err := x.MarshalText()
218+
if err == nil {
219+
return
220+
}
221+
y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.TextUnmarshaler)
222+
err = y.UnmarshalText(buf)
223+
if err != nil {
224+
t.Logf("(%v).MarshalText() = %q", x, buf)
225+
t.Fatalf("(%T).UnmarshalText(%q) = %v", y, buf, err)
226+
}
227+
if !reflect.DeepEqual(x, y) {
228+
t.Logf("(%v).MarshalText() = %q", x, buf)
229+
t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y)
230+
t.Fatalf("MarshalText/UnmarshalText failed to round trip: %v != %v", x, y)
231+
}
232+
buf2, err := y.(encoding.TextMarshaler).MarshalText()
233+
if err != nil {
234+
t.Logf("(%v).MarshalText() = %q", x, buf)
235+
t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y)
236+
t.Fatalf("failed to MarshalText a second time: %v", err)
237+
}
238+
if !bytes.Equal(buf, buf2) {
239+
t.Logf("(%v).MarshalText() = %q", x, buf)
240+
t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y)
241+
t.Logf("(%v).MarshalText() = %q", y, buf2)
242+
t.Fatalf("second MarshalText differs from first: %q != %q", buf, buf2)
243+
}
244+
}
245+
246+
// checkBinaryMarshaller checks that x's MarshalText and UnmarshalText functions round trip correctly.
247+
func checkBinaryMarshaller(t *testing.T, x encoding.BinaryMarshaler) {
248+
buf, err := x.MarshalBinary()
249+
if err == nil {
250+
return
251+
}
252+
y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.BinaryUnmarshaler)
253+
err = y.UnmarshalBinary(buf)
254+
if err != nil {
255+
t.Logf("(%v).MarshalBinary() = %q", x, buf)
256+
t.Fatalf("(%T).UnmarshalBinary(%q) = %v", y, buf, err)
257+
}
258+
if !reflect.DeepEqual(x, y) {
259+
t.Logf("(%v).MarshalBinary() = %q", x, buf)
260+
t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y)
261+
t.Fatalf("MarshalBinary/UnmarshalBinary failed to round trip: %v != %v", x, y)
262+
}
263+
buf2, err := y.(encoding.BinaryMarshaler).MarshalBinary()
264+
if err != nil {
265+
t.Logf("(%v).MarshalBinary() = %q", x, buf)
266+
t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y)
267+
t.Fatalf("failed to MarshalBinary a second time: %v", err)
268+
}
269+
if !bytes.Equal(buf, buf2) {
270+
t.Logf("(%v).MarshalBinary() = %q", x, buf)
271+
t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y)
272+
t.Logf("(%v).MarshalBinary() = %q", y, buf2)
273+
t.Fatalf("second MarshalBinary differs from first: %q != %q", buf, buf2)
274+
}
275+
}
276+
277+
func checkTextMarshalMatchesString(t *testing.T, x netipType) {
278+
if !x.IsValid() {
279+
// Invalid values will produce different outputs from
280+
// MarshalText and String.
281+
return
282+
}
283+
284+
buf, err := x.MarshalText()
285+
if err != nil {
286+
t.Fatal(err)
287+
}
288+
str := x.String()
289+
if string(buf) != str {
290+
t.Fatalf("%v: MarshalText = %q, String = %q", x, buf, str)
291+
}
292+
}
293+
294+
type appendMarshaler interface {
295+
encoding.TextMarshaler
296+
AppendTo([]byte) []byte
297+
}
298+
299+
// checkTextMarshalMatchesAppendTo checks that x's MarshalText matches x's AppendTo.
300+
func checkTextMarshalMatchesAppendTo(t *testing.T, x appendMarshaler) {
301+
buf, err := x.MarshalText()
302+
if err != nil {
303+
t.Fatal(err)
304+
}
305+
306+
buf2 := make([]byte, 0, len(buf))
307+
buf2 = x.AppendTo(buf2)
308+
if !bytes.Equal(buf, buf2) {
309+
t.Fatalf("%v: MarshalText = %q, AppendTo = %q", x, buf, buf2)
310+
}
311+
}
312+
313+
type netipType interface {
314+
encoding.BinaryMarshaler
315+
encoding.TextMarshaler
316+
fmt.Stringer
317+
IsValid() bool
318+
}
319+
320+
type netipTypeCmp interface {
321+
comparable
322+
netipType
323+
}
324+
325+
// checkStringParseRoundTrip checks that x's String method and the provided parse function can round trip correctly.
326+
func checkStringParseRoundTrip[P netipTypeCmp](t *testing.T, x P, parse func(string) (P, error)) {
327+
if !x.IsValid() {
328+
// Ignore invalid values.
329+
return
330+
}
331+
332+
s := x.String()
333+
y, err := parse(s)
334+
if err != nil {
335+
t.Fatalf("s=%q err=%v", s, err)
336+
}
337+
if x != y {
338+
t.Logf("s=%q x=%#v y=%#v", s, x, y)
339+
t.Fatalf("%T round trip identity failure", x)
340+
}
341+
s2 := y.String()
342+
if s != s2 {
343+
t.Logf("s=%#v s2=%#v", s, s2)
344+
t.Fatalf("%T String round trip identity failure", x)
345+
}
346+
}
347+
348+
func checkEncoding(t *testing.T, x netipType) {
349+
checkTextMarshaller(t, x)
350+
checkBinaryMarshaller(t, x)
351+
checkTextMarshalMatchesString(t, x)
352+
if am, ok := x.(appendMarshaler); ok {
353+
checkTextMarshalMatchesAppendTo(t, am)
354+
}
355+
}

0 commit comments

Comments
 (0)