dns/msg_helpers_test.go
Olivier Poitrey 57e2e627a6
Invalid NSEC/3 bitmap on non-zero buffer (#1338)
* Invalid NSEC/3 bitmap on non-zero buffer

If the PackBuffer is used to encode an NSEC/3 record, the bitmap is
xored with the content of the buffer instead of being zeroed first.

The algorithm has been changed so it is able zero bytes without
losing too much performance (around 2x slower).

* Add some comments + rename some vars to make algo clearer

* Revert to previous algo with window length compute+0 on new window

* Use typeBitMapLen to compute the bitmap length to zero
2022-04-01 14:01:05 +02:00

594 lines
14 KiB
Go

package dns
import (
"bytes"
"net"
"testing"
)
// TestPacketDataNsec tests generated using fuzz.go and with a message pack
// containing the following bytes: 0000\x00\x00000000\x00\x002000000\x0060000\x00\x130000000000000000000"
// That bytes sequence created the overflow error and further permutations of that sequence were able to trigger
// the other code paths.
func TestPackDataNsec(t *testing.T) {
type args struct {
bitmap []uint16
msg []byte
off int
}
tests := []struct {
name string
args args
wantOff int
wantBytes []byte
wantErr bool
wantErrMsg string
}{
{
name: "overflow",
args: args{
bitmap: []uint16{
8962, 8963, 8970, 8971, 8978, 8979,
8986, 8987, 8994, 8995, 9002, 9003,
9010, 9011, 9018, 9019, 9026, 9027,
9034, 9035, 9042, 9043, 9050, 9051,
9058, 9059, 9066,
},
msg: []byte{
48, 48, 48, 48, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 50,
48, 48, 48, 48, 48, 48,
0, 54, 48, 48, 48, 48,
0, 19, 48, 48,
},
off: 48,
},
wantErr: true,
wantErrMsg: "dns: overflow packing nsec",
wantOff: 48,
},
{
name: "disordered nsec bits",
args: args{
bitmap: []uint16{
8962,
1,
},
msg: []byte{
48, 48, 48, 48, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 50, 48, 48, 48, 48, 48, 48, 0, 54, 48,
48, 48, 48, 0, 19, 48, 48, 48, 48, 48, 48, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 50, 48, 48,
48, 48, 48, 48, 0, 54, 48, 48, 48, 48, 0, 19,
48, 48, 48, 48, 48, 48, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 50, 48, 48, 48, 48, 48, 48, 0,
54, 48, 48, 48, 48, 0, 19, 48, 48, 48, 48, 48,
48, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 50,
48, 48, 48, 48, 48, 48, 0, 54, 48, 48, 48, 48,
0, 19, 48, 48, 48, 48, 48, 48, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 50, 48, 48, 48, 48, 48,
48, 0, 54, 48, 48, 48, 48, 0, 19, 48, 48,
},
off: 0,
},
wantErr: true,
wantErrMsg: "dns: nsec bits out of order",
wantOff: 155,
},
{
name: "simple message with only one window",
args: args{
bitmap: []uint16{
1,
},
msg: []byte{
48, 48, 48, 48, 0, 0,
0, 1, 0, 0, 0, 0,
0, 0, 50, 48, 48, 48,
48, 48, 48, 0, 54, 48,
48, 48, 48, 0, 19, 48, 48,
},
off: 0,
},
wantErr: false,
wantOff: 3,
wantBytes: []byte{0, 1, 64},
},
{
name: "multiple types",
args: args{
bitmap: []uint16{
TypeNS, TypeSOA, TypeRRSIG, TypeDNSKEY, TypeNSEC3PARAM,
},
msg: []byte{
48, 48, 48, 48, 0, 0,
0, 1, 0, 0, 0, 0,
0, 0, 50, 48, 48, 48,
48, 48, 48, 0, 54, 48,
48, 48, 48, 0, 19, 48, 48,
},
off: 0,
},
wantErr: false,
wantOff: 9,
wantBytes: []byte{0, 7, 34, 0, 0, 0, 0, 2, 144},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotOff, err := packDataNsec(tt.args.bitmap, tt.args.msg, tt.args.off)
if (err != nil) != tt.wantErr {
t.Errorf("packDataNsec() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil && tt.wantErrMsg != err.Error() {
t.Errorf("packDataNsec() error msg = %v, wantErrMsg %v", err.Error(), tt.wantErrMsg)
return
}
if gotOff != tt.wantOff {
t.Errorf("packDataNsec() = %v, want off %v", gotOff, tt.wantOff)
}
if err == nil && tt.args.off < len(tt.args.msg) && gotOff < len(tt.args.msg) {
if want, got := tt.wantBytes, tt.args.msg[tt.args.off:gotOff]; !bytes.Equal(got, want) {
t.Errorf("packDataNsec() = %v, want bytes %v", got, want)
}
}
})
}
}
func TestPackDataNsecDirtyBuffer(t *testing.T) {
zeroBuf := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0}
dirtyBuf := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}
off1, _ := packDataNsec([]uint16{TypeNS, TypeSOA, TypeRRSIG}, zeroBuf, 0)
off2, _ := packDataNsec([]uint16{TypeNS, TypeSOA, TypeRRSIG}, dirtyBuf, 0)
if off1 != off2 {
t.Errorf("off1 %v != off2 %v", off1, off2)
}
if !bytes.Equal(zeroBuf[:off1], dirtyBuf[:off2]) {
t.Errorf("dirty buffer differs from zero buffer: %v, %v", zeroBuf[:off1], dirtyBuf[:off2])
}
}
func BenchmarkPackDataNsec(b *testing.B) {
benches := []struct {
name string
types []uint16
}{
{"empty", nil},
{"typical", []uint16{TypeNS, TypeSOA, TypeRRSIG, TypeDNSKEY, TypeNSEC3PARAM}},
{"multiple_windows", []uint16{1, 300, 350, 10000, 20000}},
}
for _, bb := range benches {
b.Run(bb.name, func(b *testing.B) {
buf := make([]byte, 100)
for n := 0; n < b.N; n++ {
packDataNsec(bb.types, buf, 0)
}
})
}
}
func TestUnpackString(t *testing.T) {
msg := []byte("\x00abcdef\x0f\\\"ghi\x04mmm\x7f")
msg[0] = byte(len(msg) - 1)
got, _, err := unpackString(msg, 0)
if err != nil {
t.Fatal(err)
}
if want := `abcdef\015\\\"ghi\004mmm\127`; want != got {
t.Errorf("expected %q, got %q", want, got)
}
}
func BenchmarkUnpackString(b *testing.B) {
b.Run("Escaped", func(b *testing.B) {
msg := []byte("\x00abcdef\x0f\\\"ghi\x04mmm")
msg[0] = byte(len(msg) - 1)
for n := 0; n < b.N; n++ {
got, _, err := unpackString(msg, 0)
if err != nil {
b.Fatal(err)
}
if want := `abcdef\015\\\"ghi\004mmm`; want != got {
b.Errorf("expected %q, got %q", want, got)
}
}
})
b.Run("Unescaped", func(b *testing.B) {
msg := []byte("\x00large.example.com")
msg[0] = byte(len(msg) - 1)
for n := 0; n < b.N; n++ {
got, _, err := unpackString(msg, 0)
if err != nil {
b.Fatal(err)
}
if want := "large.example.com"; want != got {
b.Errorf("expected %q, got %q", want, got)
}
}
})
}
func TestPackDataAplPrefix(t *testing.T) {
tests := []struct {
name string
negation bool
ip net.IP
mask net.IPMask
expect []byte
}{
{
"1:192.0.2.0/24",
false,
net.ParseIP("192.0.2.0").To4(),
net.CIDRMask(24, 32),
[]byte{0x00, 0x01, 0x18, 0x03, 192, 0, 2},
},
{
"2:2001:db8:cafe::0/48",
false,
net.ParseIP("2001:db8:cafe::"),
net.CIDRMask(48, 128),
[]byte{0x00, 0x02, 0x30, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe},
},
{
"with trailing zero bytes 2:2001:db8:cafe::0/64",
false,
net.ParseIP("2001:db8:cafe::"),
net.CIDRMask(64, 128),
[]byte{0x00, 0x02, 0x40, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe},
},
{
"no non-zero bytes 2::/16",
false,
net.ParseIP("::"),
net.CIDRMask(16, 128),
[]byte{0x00, 0x02, 0x10, 0x00},
},
{
"!2:2001:db8::/32",
true,
net.ParseIP("2001:db8::"),
net.CIDRMask(32, 128),
[]byte{0x00, 0x02, 0x20, 0x84, 0x20, 0x01, 0x0d, 0xb8},
},
{
"normalize 1:198.51.103.255/22",
false,
net.ParseIP("198.51.103.255").To4(),
net.CIDRMask(22, 32),
[]byte{0x00, 0x01, 0x16, 0x03, 198, 51, 100}, // 1:198.51.100.0/22
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ap := &APLPrefix{
Negation: tt.negation,
Network: net.IPNet{IP: tt.ip, Mask: tt.mask},
}
out := make([]byte, 16)
off, err := packDataAplPrefix(ap, out, 0)
if err != nil {
t.Fatalf("expected no error, got %q", err)
}
if !bytes.Equal(tt.expect, out[:off]) {
t.Fatalf("expected output %02x, got %02x", tt.expect, out[:off])
}
// Make sure the packed bytes would be accepted by its own unpack
_, _, err = unpackDataAplPrefix(out, 0)
if err != nil {
t.Fatalf("expected no error, got %q", err)
}
})
}
}
func TestPackDataAplPrefix_Failures(t *testing.T) {
tests := []struct {
name string
ip net.IP
mask net.IPMask
}{
{
"family mismatch",
net.ParseIP("2001:db8::"),
net.CIDRMask(24, 32),
},
{
"unrecognized family",
[]byte{0x42},
[]byte{0xff},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ap := &APLPrefix{Network: net.IPNet{IP: tt.ip, Mask: tt.mask}}
msg := make([]byte, 16)
off, err := packDataAplPrefix(ap, msg, 0)
if err == nil {
t.Fatal("expected error, got none")
}
if off != len(msg) {
t.Fatalf("expected %d, got %d", len(msg), off)
}
})
}
}
func TestPackDataAplPrefix_BufferBounds(t *testing.T) {
ap := &APLPrefix{
Negation: false,
Network: net.IPNet{
IP: net.ParseIP("2001:db8::"),
Mask: net.CIDRMask(32, 128),
},
}
wire := []byte{0x00, 0x02, 0x20, 0x04, 0x20, 0x01, 0x0d, 0xb8}
t.Run("small", func(t *testing.T) {
msg := make([]byte, len(wire))
_, err := packDataAplPrefix(ap, msg, 1) // offset
if err == nil {
t.Fatal("expected error, got none")
}
})
t.Run("exact fit", func(t *testing.T) {
msg := make([]byte, len(wire))
off, err := packDataAplPrefix(ap, msg, 0)
if err != nil {
t.Fatalf("expected no error, got %q", err)
}
if !bytes.Equal(wire, msg[:off]) {
t.Fatalf("expected %02x, got %02x", wire, msg[:off])
}
})
}
func TestPackDataApl(t *testing.T) {
in := []APLPrefix{
{
Negation: true,
Network: net.IPNet{
IP: net.ParseIP("198.51.0.0").To4(),
Mask: net.CIDRMask(16, 32),
},
},
{
Negation: false,
Network: net.IPNet{
IP: net.ParseIP("2001:db8:beef::"),
Mask: net.CIDRMask(48, 128),
},
},
}
expect := []byte{
// 1:192.51.0.0/16
0x00, 0x01, 0x10, 0x82, 0xc6, 0x33,
// 2:2001:db8:beef::0/48
0x00, 0x02, 0x30, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xbe, 0xef,
}
msg := make([]byte, 32)
off, err := packDataApl(in, msg, 0)
if err != nil {
t.Fatalf("expected no error, got %q", err)
}
if !bytes.Equal(expect, msg[:off]) {
t.Fatalf("expected %02x, got %02x", expect, msg[:off])
}
}
func TestUnpackDataAplPrefix(t *testing.T) {
tests := []struct {
name string
wire []byte
negation bool
ip net.IP
mask net.IPMask
}{
{
"1:192.0.2.0/24",
[]byte{0x00, 0x01, 0x18, 0x03, 192, 0, 2},
false,
net.ParseIP("192.0.2.0").To4(),
net.CIDRMask(24, 32),
},
{
"2:2001:db8::/32",
[]byte{0x00, 0x02, 0x20, 0x04, 0x20, 0x01, 0x0d, 0xb8},
false,
net.ParseIP("2001:db8::"),
net.CIDRMask(32, 128),
},
{
"!2:2001:db8:8000::/33",
[]byte{0x00, 0x02, 0x21, 0x85, 0x20, 0x01, 0x0d, 0xb8, 0x80},
true,
net.ParseIP("2001:db8:8000::"),
net.CIDRMask(33, 128),
},
{
"1:0.0.0.0/0",
[]byte{0x00, 0x01, 0x00, 0x00},
false,
net.ParseIP("0.0.0.0").To4(),
net.CIDRMask(0, 32),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, off, err := unpackDataAplPrefix(tt.wire, 0)
if err != nil {
t.Fatalf("expected no error, got %q", err)
}
if off != len(tt.wire) {
t.Fatalf("expected offset %d, got %d", len(tt.wire), off)
}
if got.Negation != tt.negation {
t.Errorf("expected negation %v, got %v", tt.negation, got.Negation)
}
if !bytes.Equal(got.Network.IP, tt.ip) {
t.Errorf("expected IP %02x, got %02x", tt.ip, got.Network.IP)
}
if !bytes.Equal(got.Network.Mask, tt.mask) {
t.Errorf("expected mask %02x, got %02x", tt.mask, got.Network.Mask)
}
})
}
}
func TestUnpackDataAplPrefix_Errors(t *testing.T) {
tests := []struct {
name string
wire []byte
want string
}{
{
"incomplete header",
[]byte{0x00, 0x01, 0x18},
"dns: overflow unpacking APL prefix",
},
{
"unrecognized family",
[]byte{0x00, 0x03, 0x00, 0x00},
"dns: unrecognized APL address family",
},
{
"prefix too large for family IPv4",
[]byte{0x00, 0x01, 0x21, 0x04, 192, 0, 2, 0},
"dns: APL prefix too long",
},
{
"prefix too large for family IPv6",
[]byte{0x00, 0x02, 0x81, 0x85, 0x20, 0x01, 0x0d, 0xb8, 0x80},
"dns: APL prefix too long",
},
{
"afdlen too long for address family IPv4",
[]byte{0x00, 0x01, 22, 0x05, 192, 0, 2, 0, 0},
"dns: APL length too long",
},
{
"overflow unpacking APL address",
[]byte{0x00, 0x01, 0x10, 0x02, 192},
"dns: overflow unpacking APL address",
},
{
"address included trailing zeros",
[]byte{0x00, 0x01, 0x10, 0x04, 192, 0, 2, 0},
"dns: extra APL address bits",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, _, err := unpackDataAplPrefix(tt.wire, 0)
if err == nil {
t.Fatal("expected error, got none")
}
if err.Error() != tt.want {
t.Errorf("expected %s, got %s", tt.want, err.Error())
}
})
}
}
func TestUnpackDataApl(t *testing.T) {
wire := []byte{
// 2:2001:db8:cafe:4200:0/56
0x00, 0x02, 0x38, 0x07, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0x42,
// 1:192.0.2.0/24
0x00, 0x01, 0x18, 0x03, 192, 0, 2,
// !1:192.0.2.128/25
0x00, 0x01, 0x19, 0x84, 192, 0, 2, 128,
// 1:10.0.0.0/24
0x00, 0x01, 0x18, 0x01, 0x0a,
// !1:10.0.0.1/32
0x00, 0x01, 0x20, 0x84, 0x0a, 0, 0, 1,
// !1:0.0.0.0/0
0x00, 0x01, 0x00, 0x80,
// 2::0/0
0x00, 0x02, 0x00, 0x00,
}
expect := []APLPrefix{
{
Negation: false,
Network: net.IPNet{
IP: net.ParseIP("2001:db8:cafe:4200::"),
Mask: net.CIDRMask(56, 128),
},
},
{
Negation: false,
Network: net.IPNet{
IP: net.ParseIP("192.0.2.0").To4(),
Mask: net.CIDRMask(24, 32),
},
},
{
Negation: true,
Network: net.IPNet{
IP: net.ParseIP("192.0.2.128").To4(),
Mask: net.CIDRMask(25, 32),
},
},
{
Negation: false,
Network: net.IPNet{
IP: net.ParseIP("10.0.0.0").To4(),
Mask: net.CIDRMask(24, 32),
},
},
{
Negation: true,
Network: net.IPNet{
IP: net.ParseIP("10.0.0.1").To4(),
Mask: net.CIDRMask(32, 32),
},
},
{
Negation: true,
Network: net.IPNet{
IP: net.ParseIP("0.0.0.0").To4(),
Mask: net.CIDRMask(0, 32),
},
},
{
Negation: false,
Network: net.IPNet{
IP: net.ParseIP("::").To16(),
Mask: net.CIDRMask(0, 128),
},
},
}
got, off, err := unpackDataApl(wire, 0)
if err != nil {
t.Fatalf("expected no error, got %q", err)
}
if off != len(wire) {
t.Fatalf("expected offset %d, got %d", len(wire), off)
}
if len(got) != len(expect) {
t.Fatalf("expected %d prefixes, got %d", len(expect), len(got))
}
for i, exp := range expect {
if got[i].Negation != exp.Negation {
t.Errorf("[%d] expected negation %v, got %v", i, exp.Negation, got[i].Negation)
}
if !bytes.Equal(got[i].Network.IP, exp.Network.IP) {
t.Errorf("[%d] expected IP %02x, got %02x", i, exp.Network.IP, got[i].Network.IP)
}
if !bytes.Equal(got[i].Network.Mask, exp.Network.Mask) {
t.Errorf("[%d] expected mask %02x, got %02x", i, exp.Network.Mask, got[i].Network.Mask)
}
}
}