mirror of
https://github.com/tailscale/tailscale.git
synced 2026-10-09 11:52:00 -04:00
This change expands our fuzzing coverage in protocol and parsing logic. No issues discovered from this fuzzing. Wiring into oss-fuzz for continual coverage. Updates https://github.com/tailscale/corp/issues/46608 Change-Id: I6b5218cb1103ccc5b957c512a10d87f637c4b6e5 Signed-off-by: Mike Jensen <mikej@tailscale.com>
110 lines
4.2 KiB
Go
110 lines
4.2 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package derp
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"testing"
|
|
|
|
"tailscale.com/types/key"
|
|
"tailscale.com/types/logger"
|
|
)
|
|
|
|
func FuzzReadFrameHeader(f *testing.F) {
|
|
f.Add([]byte{byte(FramePing), 0, 1, 2, 3})
|
|
// A complete ping frame header: type + big-endian length of 8
|
|
f.Add([]byte{byte(FramePing), 0, 0, 0, 8})
|
|
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
|
_, _, _ = ReadFrameHeader(bufio.NewReader(bytes.NewReader(data)))
|
|
})
|
|
}
|
|
|
|
func FuzzReadFrame(f *testing.F) {
|
|
f.Add([]byte{byte(FramePing), 0, 1, 2, 3})
|
|
// A complete ping frame: header plus its 8-byte payload
|
|
f.Add(append([]byte{byte(FramePing), 0, 0, 0, 8}, 1, 2, 3, 4, 5, 6, 7, 8))
|
|
// A frame longer than the 40-byte scratch buffer (exercises ErrShortBuffer)
|
|
f.Add(append([]byte{byte(FramePing), 0, 0, 0, 50}, make([]byte, 50)...))
|
|
|
|
const maxSize = 1 << 10
|
|
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
|
_, _, _ = readFrame(bufio.NewReader(bytes.NewReader(data)), maxSize, make([]byte, 40))
|
|
})
|
|
}
|
|
|
|
func FuzzRecvTimeout(f *testing.F) {
|
|
// A minimal valid frame stream: a keep-alive followed by EOF
|
|
f.Add([]byte{byte(FrameKeepAlive), 0, 1, 2, 3})
|
|
// A valid keep-alive (zero-length frame)
|
|
f.Add([]byte{byte(FrameKeepAlive), 0, 0, 0, 0})
|
|
// A valid ping frame with its 8-byte payload
|
|
f.Add(append([]byte{byte(FramePing), 0, 0, 0, 8}, 1, 2, 3, 4, 5, 6, 7, 8))
|
|
// A FramePeerGone with a 32-byte peer key and reason byte
|
|
f.Add(append(append([]byte{byte(FramePeerGone), 0, 0, 0, 33}, make([]byte, 32)...), 0))
|
|
// A FrameRecvPacket with a 32-byte source key plus packet payload
|
|
f.Add(append(append([]byte{byte(FrameRecvPacket), 0, 0, 0, 36}, make([]byte, 32)...), 1, 2, 3, 4))
|
|
// A FrameRestarting with the two big-endian duration payloads
|
|
f.Add([]byte{byte(FrameRestarting), 0, 0, 0, 8, 0, 0, 0x0e, 0x10, 0, 0, 0x3a, 0x98})
|
|
// A keep-alive followed by a ping frame (multi-frame stream)
|
|
f.Add([]byte{byte(FrameKeepAlive), 0, 0, 0, 0, byte(FramePing), 0, 0, 0, 8, 1, 2, 3, 4, 5, 6, 7, 8})
|
|
// A valid pong frame with its 8-byte payload
|
|
f.Add(append([]byte{byte(FramePong), 0, 0, 0, 8}, 1, 2, 3, 4, 5, 6, 7, 8))
|
|
// A health-problem frame with a text payload
|
|
f.Add(append([]byte{byte(FrameHealth), 0, 0, 0, 4}, 'd', 'e', 'r', 'p'))
|
|
// A PeerPresent frame: 32-byte key + IPv6 addr/port + flags + app name
|
|
f.Add(append(append([]byte{byte(FramePeerPresent), 0, 0, 0, 0x34}, make([]byte, 32)...),
|
|
0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1f, 0x90,
|
|
0x00, 0x04, 't', 'a', 'i', 'l'))
|
|
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
|
priv := key.NewNode()
|
|
c := &Client{
|
|
privateKey: priv,
|
|
serverKey: priv.Public(),
|
|
logf: logger.Discard,
|
|
nc: dummyNetConn{},
|
|
br: bufio.NewReader(bytes.NewReader(data)),
|
|
}
|
|
_, _ = c.recvTimeout(0)
|
|
})
|
|
}
|
|
|
|
func FuzzRecvServerKey(f *testing.F) {
|
|
f.Add([]byte{byte(FrameServerKey), 1, 2, 3, 4})
|
|
// A valid server greeting: type, big-endian length 40, magic, 32-byte key
|
|
var greeting [5 + 8 + 32]byte
|
|
greeting[0] = byte(FrameServerKey)
|
|
binary.BigEndian.PutUint32(greeting[1:5], 40)
|
|
copy(greeting[5:13], Magic)
|
|
f.Add(greeting[:])
|
|
// Right length but bad magic
|
|
f.Add(append(append([]byte{byte(FrameServerKey), 0, 0, 0, 40},
|
|
0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58), make([]byte, 32)...))
|
|
// A greeting shorter than 40 bytes
|
|
f.Add(append([]byte{byte(FrameServerKey), 0, 0, 0, 39}, make([]byte, 39)...))
|
|
// Zero-length greeting
|
|
f.Add([]byte{byte(FrameServerKey), 0, 0, 0, 0})
|
|
// Wrong frame type with an otherwise-valid body
|
|
f.Add(append(append([]byte{byte(FrameKeepAlive), 0, 0, 0, 40}, Magic...), make([]byte, 32)...))
|
|
// Length over the 1KiB readFrame limit
|
|
f.Add([]byte{byte(FrameServerKey), 0, 0, 0x04, 0x01})
|
|
// Truncated payload after the header
|
|
f.Add(append([]byte{byte(FrameServerKey), 0, 0, 0, 40}, Magic...))
|
|
// A future-proof long greeting (50 bytes): ErrShortBuffer tolerated
|
|
f.Add(append(append(append([]byte{byte(FrameServerKey), 0, 0, 0, 50}, Magic...),
|
|
make([]byte, 32)...), make([]byte, 10)...))
|
|
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
|
c := &Client{
|
|
logf: logger.Discard,
|
|
br: bufio.NewReader(bytes.NewReader(data)),
|
|
}
|
|
_ = c.recvServerKey()
|
|
})
|
|
}
|