diff --git a/CHANGELOG.md b/CHANGELOG.md index 01879d3..36abdc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,10 @@ verdict. `https://[[2000::]/`, whose brackets `checkHost` trimmed all at once. `isCIDv1` refuses 5 or more bits left over, and `checkHost` takes one pair of brackets. No normative change: `TestAddressCanonicalForms`. +- **Fuzzing.** `FuzzCheckResolvedIP`, the 26th target of `scripts/fuzz.sh`: + an address that `CheckResolvedIP` accepts is public, or holds a public + IPv4 address at the positions of RFC 6052 in the NAT64 prefix that + contains it, whatever the prefix given. - **Test data.** `vectors/resolved_ip.json`, new: 42 addresses, with the prefix of the network or none, and the result of `CheckResolvedIP`, with its text. The other files do not change. diff --git a/locator/fuzz_test.go b/locator/fuzz_test.go index 2bc6954..cbebb09 100644 --- a/locator/fuzz_test.go +++ b/locator/fuzz_test.go @@ -3,6 +3,7 @@ package locator_test import ( "bytes" "encoding/hex" + "net/netip" "testing" "g.activething.com/go/DateKeys/extension" @@ -83,3 +84,59 @@ func FuzzCheckURI(f *testing.F) { } }) } + +// FuzzCheckResolvedIP checks the address a name resolves to, with a NAT64 +// prefix of the network or none (spec v0.13, ยง44.1): it never panics, and an +// address it accepts is public, or holds a public IPv4 address at the +// positions of RFC 6052 in 64:ff9b::/96 or in the prefix, with bits 64 to +// 71 zero. No prefix ever lets an address that holds a private IPv4 address +// through. +func FuzzCheckResolvedIP(f *testing.F) { + f.Add(net16("64:ff9b::cb00:7205"), net16("64:ff9b:1::"), uint8(48), false) + f.Add(net16("64:ff9b:1:c0a8:1:a00::"), net16("64:ff9b:1::"), uint8(48), true) + f.Add(net16("2a01:4f8:c0:64::c0a8:10a"), net16("2a01:4f8:c0:64::"), uint8(96), true) + f.Add(net16("2a01:4f8::1"), net16("::"), uint8(0), false) + f.Fuzz(func(t *testing.T, ipb, pb []byte, bits uint8, withPrefix bool) { + if len(ipb) != 16 || len(pb) != 16 { + return + } + ip := netip.AddrFrom16([16]byte(ipb)) + var p netip.Prefix + if withPrefix { + p = netip.PrefixFrom(netip.AddrFrom16([16]byte(pb)), int(bits)) + } + if locator.CheckResolvedIP(ip, p) != nil { + return + } + public := locator.CheckResolvedIP(ip, netip.Prefix{}) == nil + for _, q := range []netip.Prefix{netip.MustParsePrefix("64:ff9b::/96"), p} { + if !q.IsValid() || !q.Contains(ip) { + continue + } + b := ip.As16() + if q.Bits() < 96 && b[8] != 0 { + t.Fatalf("%s accepted with %v: bits 64 to 71 set", ip, p) + } + var v4 [4]byte + n := 0 + for i := q.Bits() / 8; n < 4; i++ { + if i != 8 { + v4[n] = b[i] + n++ + } + } + if locator.CheckResolvedIP(netip.AddrFrom4(v4), netip.Prefix{}) != nil { + t.Fatalf("%s accepted with %v: it holds %v, which is not public", ip, p, netip.AddrFrom4(v4)) + } + return + } + if !public { + t.Fatalf("%s accepted with %v, outside every NAT64 prefix, and not public", ip, p) + } + }) +} + +func net16(s string) []byte { + b := netip.MustParseAddr(s).As16() + return b[:] +} diff --git a/scripts/fuzz.sh b/scripts/fuzz.sh index da58188..716cf74 100644 --- a/scripts/fuzz.sh +++ b/scripts/fuzz.sh @@ -31,6 +31,7 @@ targets=( "./locator FuzzUnmarshal" "./locator FuzzParseInfo" "./locator FuzzCheckURI" + "./locator FuzzCheckResolvedIP" "./internal/der FuzzDERCheck" "./internal/cms FuzzParseSignature" "./internal/cms FuzzParseToken"