snapshot of https://github.com/nats-io/nats-server.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
NATS Server Tests / Lint (push) Failing after 3s
NATS Server Tests / Build (Latest Go) (push) Has been skipped
NATS Server Tests / Build (Minimum Go) (push) Has been skipped
NATS Server Tests / Build (Minimum Go, windows-2022) (push) Has been skipped
NATS Server Tests / Build (Minimum Go, windows-2025) (push) Has been skipped
NATS Server Tests / Test Stores (push) Has been skipped
NATS Server Tests / Test JetStream (push) Has been skipped
NATS Server Tests / Test Raft (push) Has been skipped
NATS Server Tests / Test JetStream Consumers (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 1 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 2 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 3 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 4 (push) Has been skipped
NATS Server Tests / Test JetStream Supercluster (push) Has been skipped
NATS Server Tests / Test No-Race 1 (push) Has been skipped
NATS Server Tests / Test No-Race 2 (push) Has been skipped
NATS Server Tests / Test MQTT (push) Has been skipped
NATS Server Tests / Test Message Tracing (push) Has been skipped
NATS Server Tests / Test JWT (push) Has been skipped
NATS Server Tests / Test Remaining Server Tests (push) Has been skipped
NATS Server Tests / Test Other Packages (push) Has been skipped
Vulnerability Scan / govulncheck (push) Failing after 12m2s
Some checks failed
NATS Server Tests / Lint (push) Failing after 3s
NATS Server Tests / Build (Latest Go) (push) Has been skipped
NATS Server Tests / Build (Minimum Go) (push) Has been skipped
NATS Server Tests / Build (Minimum Go, windows-2022) (push) Has been skipped
NATS Server Tests / Build (Minimum Go, windows-2025) (push) Has been skipped
NATS Server Tests / Test Stores (push) Has been skipped
NATS Server Tests / Test JetStream (push) Has been skipped
NATS Server Tests / Test Raft (push) Has been skipped
NATS Server Tests / Test JetStream Consumers (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 1 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 2 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 3 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 4 (push) Has been skipped
NATS Server Tests / Test JetStream Supercluster (push) Has been skipped
NATS Server Tests / Test No-Race 1 (push) Has been skipped
NATS Server Tests / Test No-Race 2 (push) Has been skipped
NATS Server Tests / Test MQTT (push) Has been skipped
NATS Server Tests / Test Message Tracing (push) Has been skipped
NATS Server Tests / Test JWT (push) Has been skipped
NATS Server Tests / Test Remaining Server Tests (push) Has been skipped
NATS Server Tests / Test Other Packages (push) Has been skipped
Vulnerability Scan / govulncheck (push) Failing after 12m2s
This commit is contained in:
410
server/server_fuzz_test.go
Normal file
410
server/server_fuzz_test.go
Normal file
@@ -0,0 +1,410 @@
|
||||
// Copyright 2025 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
tlsHandshake byte = 22
|
||||
tlsClientHello byte = 1
|
||||
)
|
||||
|
||||
type ClientHelloInjector struct {
|
||||
sock io.ReadWriteCloser
|
||||
tlsVersion uint16
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func NewClientHelloInjector(s io.ReadWriteCloser, tlsVer uint16, b []byte) *ClientHelloInjector {
|
||||
return &ClientHelloInjector{
|
||||
sock: s,
|
||||
tlsVersion: tlsVer,
|
||||
buf: b,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ClientHelloInjector) inject(b []byte) []byte {
|
||||
if !(b != nil && b[0] == tlsHandshake) {
|
||||
return b
|
||||
}
|
||||
|
||||
hsLen := (uint16(b[3]) << 8) + uint16(b[4])
|
||||
|
||||
if !(hsLen > 0 && b[5] == tlsClientHello) {
|
||||
return b
|
||||
}
|
||||
|
||||
// fuzz tls version in client hello
|
||||
b[9] = uint8(i.tlsVersion >> 8)
|
||||
b[10] = uint8(i.tlsVersion & 0xFF)
|
||||
|
||||
// Go to begin of random opaque
|
||||
offset := 11
|
||||
|
||||
randomOpaque := b[offset : offset+32]
|
||||
|
||||
copy(randomOpaque, i.buf)
|
||||
|
||||
offset += 32
|
||||
|
||||
sessionIDLen := b[offset]
|
||||
|
||||
// Skip session id len + opaque
|
||||
offset += 1 + int(sessionIDLen)
|
||||
|
||||
cypherSuiteLen := (uint16(b[offset]) << 8) + uint16(b[offset+1])
|
||||
|
||||
// Skip cypherSuiteLen
|
||||
offset += 2
|
||||
|
||||
cupherSuites := b[offset : offset+int(cypherSuiteLen)]
|
||||
|
||||
// Leave unchanged if i.cypherSuites empty
|
||||
copy(cupherSuites, i.buf)
|
||||
|
||||
// Skip cypherSuites
|
||||
offset += int(cypherSuiteLen)
|
||||
|
||||
// Skip CompressionMethod
|
||||
offset += 2
|
||||
|
||||
extensionsLen := (uint16(b[offset]) << 8) + uint16(b[offset+1])
|
||||
|
||||
// Skip extensions length
|
||||
offset += 2
|
||||
|
||||
// Extensions slice. Stub for future use
|
||||
_ = b[offset : offset+int(extensionsLen)]
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (i *ClientHelloInjector) Write(b []byte) (int, error) {
|
||||
return i.sock.Write(i.inject(b))
|
||||
}
|
||||
|
||||
func (i *ClientHelloInjector) Read(b []byte) (int, error) {
|
||||
return i.sock.Read(b)
|
||||
}
|
||||
|
||||
func (i *ClientHelloInjector) Close() error {
|
||||
return i.sock.Close()
|
||||
}
|
||||
|
||||
type FakeSocket struct {
|
||||
sockName string
|
||||
buf []byte
|
||||
data chan []byte
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func NewFakeSocket(name string, capacity int) *FakeSocket {
|
||||
return &FakeSocket{
|
||||
sockName: name,
|
||||
data: make(chan []byte, capacity),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FakeSocket) Write(b []byte) (int, error) {
|
||||
select {
|
||||
case s.data <- b:
|
||||
return len(b), nil
|
||||
case <-s.done:
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FakeSocket) readChunk(b []byte) (int, error) {
|
||||
n := copy(b, s.buf)
|
||||
s.buf = s.buf[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (s *FakeSocket) Read(b []byte) (int, error) {
|
||||
if len(s.buf) > 0 {
|
||||
return s.readChunk(b)
|
||||
}
|
||||
|
||||
select {
|
||||
case buf, ok := <-s.data:
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
s.buf = buf
|
||||
return s.readChunk(b)
|
||||
case <-s.done:
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FakeSocket) Close() error {
|
||||
close(s.done)
|
||||
return nil
|
||||
}
|
||||
|
||||
type FakeConn struct {
|
||||
local io.ReadWriteCloser
|
||||
remote io.ReadWriteCloser
|
||||
}
|
||||
|
||||
func NewFakeConn(loc io.ReadWriteCloser, rem io.ReadWriteCloser) *FakeConn {
|
||||
return &FakeConn{
|
||||
local: loc,
|
||||
remote: rem,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FakeConn) Read(b []byte) (int, error) {
|
||||
return c.local.Read(b)
|
||||
}
|
||||
|
||||
func (c *FakeConn) Write(b []byte) (int, error) {
|
||||
return c.remote.Write(b)
|
||||
}
|
||||
|
||||
func (c *FakeConn) Close() error {
|
||||
return c.local.Close()
|
||||
}
|
||||
|
||||
func (c *FakeConn) LocalAddr() net.Addr {
|
||||
return &net.TCPAddr{IP: net.IP{127, 0, 0, 1}, Port: 4222, Zone: ""}
|
||||
}
|
||||
|
||||
func (c *FakeConn) RemoteAddr() net.Addr {
|
||||
return &net.TCPAddr{IP: net.IP{127, 0, 0, 1}, Port: 4222, Zone: ""}
|
||||
}
|
||||
|
||||
func (c *FakeConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *FakeConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *FakeConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type FakeListener struct {
|
||||
ch chan *FakeConn
|
||||
acceptErr error
|
||||
}
|
||||
|
||||
func NewFakeListener() *FakeListener {
|
||||
return &FakeListener{
|
||||
ch: make(chan *FakeConn),
|
||||
acceptErr: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (ln *FakeListener) Accept() (c net.Conn, err error) {
|
||||
return <-ln.ch, ln.acceptErr
|
||||
}
|
||||
|
||||
func (ln *FakeListener) Close() error {
|
||||
ln.acceptErr = io.EOF
|
||||
close(ln.ch)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ln *FakeListener) Addr() net.Addr {
|
||||
return &net.TCPAddr{IP: net.IP{127, 0, 0, 1}, Port: 4222, Zone: ""}
|
||||
}
|
||||
|
||||
func getTlsVersion(useTls13 bool) uint16 {
|
||||
if useTls13 {
|
||||
return tls.VersionTLS13
|
||||
}
|
||||
|
||||
return tls.VersionTLS12
|
||||
}
|
||||
|
||||
func corruptCert(crt []byte, i uint16) []byte {
|
||||
crt[int(i)%len(crt)] ^= 0xFF
|
||||
return crt
|
||||
}
|
||||
|
||||
func runServerWithListener(ln net.Listener, opts *Options) *Server {
|
||||
if opts == nil {
|
||||
opts = DefaultOptions()
|
||||
}
|
||||
s, err := NewServer(opts)
|
||||
if err != nil || s == nil {
|
||||
panic(fmt.Sprintf("No NATS Server object returned: %v", err))
|
||||
}
|
||||
|
||||
if !opts.NoLog {
|
||||
s.ConfigureLogger()
|
||||
}
|
||||
|
||||
s.listener = ln
|
||||
s.listenerErr = nil
|
||||
|
||||
// Run server in Go routine.
|
||||
s.Start()
|
||||
|
||||
// Wait for accept loop(s) to be started
|
||||
if err := s.readyForConnections(10 * time.Second); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type MathRandReader byte
|
||||
|
||||
func (m MathRandReader) Read(buf []byte) (int, error) {
|
||||
for i := range buf {
|
||||
buf[i] = byte(m)
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
// FuzzServerTLS performs fuzz testing of the NATS server's TLS handshake implementation.
|
||||
// It verifies the server's ability to handle various TLS connection scenarios, including:
|
||||
// - Different TLS versions (1.2 and 1.3)
|
||||
// - Malformed/mutated client certificates
|
||||
// - Corrupted TLS handshake data
|
||||
// - Edge cases in the TLS negotiation process
|
||||
//
|
||||
// Test Setup:
|
||||
// - Configures a server with mutual TLS authentication using test certificates
|
||||
// - Creates a client with configurable TLS parameters
|
||||
// - Uses fake network connections to inject test cases
|
||||
//
|
||||
// Fuzzing Parameters:
|
||||
// - useTls13: Boolean flag to test TLS 1.3 (true) or TLS 1.2 (false)
|
||||
// - tlsVer: TLS version number to use in ClientHello
|
||||
// - buf: Additional bytes to inject into ClientHello message
|
||||
// - corruptCertOffset: Position to corrupt in client certificate (MaxUint16 = no corruption)
|
||||
//
|
||||
// Expectations
|
||||
// The server should either:
|
||||
// - Successfully complete the TLS handshake and protocol exchange, or
|
||||
// - Cleanly reject invalid connections without crashing
|
||||
func FuzzServerTLS(f *testing.F) {
|
||||
srvTc := &TLSConfigOpts{
|
||||
CertFile: "../test/configs/certs/tlsauth/server.pem",
|
||||
KeyFile: "../test/configs/certs/tlsauth/server-key.pem",
|
||||
CaFile: "../test/configs/certs/tlsauth/ca.pem",
|
||||
Insecure: false,
|
||||
Verify: true,
|
||||
}
|
||||
|
||||
srvTlsCfg, err := GenTLSConfig(srvTc)
|
||||
if err != nil {
|
||||
f.Fatalf("Error generating server tls config: %v", err)
|
||||
}
|
||||
|
||||
opts := &Options{
|
||||
Host: "127.0.0.1",
|
||||
Port: 4222,
|
||||
NoLog: true,
|
||||
NoSigs: true,
|
||||
Debug: true,
|
||||
Trace: true,
|
||||
TLSHandshakeFirst: true,
|
||||
AllowNonTLS: false,
|
||||
JetStream: false,
|
||||
TLSConfig: srvTlsCfg,
|
||||
CheckConfig: false,
|
||||
}
|
||||
|
||||
clientCerts, err := tls.LoadX509KeyPair("../test/configs/certs/tlsauth/client.pem", "../test/configs/certs/tlsauth/client-key.pem")
|
||||
if err != nil {
|
||||
f.Fatalf("client1 certificate load error: %s", err)
|
||||
}
|
||||
|
||||
clientTlsCfg := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Rand: MathRandReader(0),
|
||||
Time: func() time.Time { return time.Date(2025, 1, 1, 1, 1, 1, 1, nil) },
|
||||
}
|
||||
|
||||
tlsVer := uint16(0x0303)
|
||||
|
||||
corpuses := []struct {
|
||||
useTls13 bool
|
||||
clientHelloTlsVer uint16
|
||||
buf []byte
|
||||
corruptCertOffset uint16
|
||||
}{
|
||||
{useTls13: false, clientHelloTlsVer: tlsVer, buf: []byte{}, corruptCertOffset: math.MaxUint16},
|
||||
{useTls13: true, clientHelloTlsVer: tlsVer, buf: []byte{}, corruptCertOffset: math.MaxUint16},
|
||||
}
|
||||
|
||||
for _, crp := range corpuses {
|
||||
f.Add(crp.useTls13, crp.clientHelloTlsVer, crp.buf, crp.corruptCertOffset)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, useTls13 bool, tlsVer uint16, buf []byte, corruptCertOffset uint16) {
|
||||
ln := NewFakeListener()
|
||||
s := runServerWithListener(ln, opts.Clone())
|
||||
defer s.Shutdown()
|
||||
|
||||
clientSocket := NewFakeSocket("CLIENT", 8)
|
||||
serverSocket := NewClientHelloInjector(NewFakeSocket("SERVER", 8), tlsVer, buf)
|
||||
|
||||
clientConn := NewFakeConn(clientSocket, serverSocket)
|
||||
serverConn := NewFakeConn(serverSocket, clientSocket)
|
||||
|
||||
// Connect to server
|
||||
ln.ch <- serverConn
|
||||
|
||||
tlsVersion := getTlsVersion(useTls13)
|
||||
|
||||
tlsCfg := clientTlsCfg.Clone()
|
||||
tlsCfg.GetClientCertificate = func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
if corruptCertOffset == math.MaxUint16 {
|
||||
t.Log("Leave certificate unchanged")
|
||||
return &clientCerts, nil
|
||||
}
|
||||
|
||||
origCert := clientCerts.Certificate[0]
|
||||
newCert := make([]byte, len(origCert))
|
||||
copy(newCert, origCert)
|
||||
|
||||
newTlsCerts := clientCerts
|
||||
newTlsCerts.Certificate[0] = corruptCert(newCert, corruptCertOffset)
|
||||
|
||||
return &newTlsCerts, nil
|
||||
}
|
||||
tlsCfg.MaxVersion = tlsVersion
|
||||
tlsCfg.MinVersion = tlsVersion
|
||||
|
||||
tlsClientConn := tls.Client(clientConn, tlsCfg)
|
||||
defer tlsClientConn.Close()
|
||||
|
||||
if err := tlsClientConn.Handshake(); err != nil {
|
||||
t.Logf("Handshake error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
br := bufio.NewReaderSize(tlsClientConn, 128)
|
||||
if _, err := br.ReadString('\n'); err != nil {
|
||||
t.Logf("Unexpected error reading INFO message: %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user