mirror of
https://blitiri.com.ar/repos/chasquid
synced 2026-01-08 17:51:57 +00:00
test: Update fuzz tests to use the built-in infrastructure
Go 1.18 supports fuzzing natively, so this patch migrates our fuzzing tests to make use of it.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package aliases
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -482,3 +483,10 @@ func TestHookError(t *testing.T) {
|
||||
t.Errorf("expected *exec.ExitError, got %T - %v", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Fuzz testing for the parser.
|
||||
func FuzzReader(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
parseReader("domain", bytes.NewReader(data))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// Fuzz testing for package aliases.
|
||||
|
||||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
package aliases
|
||||
|
||||
import "bytes"
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
interesting := 0
|
||||
aliases, _ := parseReader("domain", bytes.NewReader(data))
|
||||
|
||||
// Mark cases with actual aliases as more interesting.
|
||||
for _, rcpts := range aliases {
|
||||
if len(rcpts) > 0 {
|
||||
interesting = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return interesting
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("# First some valid cases.\na: b\nc: d@e, f,\nx: | command\n\n# The following is invalid, should be ignored.\na@dom: x@dom\n\n# Overrides.\no1: a\no1: b\n\n# Finally one to make the file NOT end in \\n:\ny: z\n")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("\nfail: | false\n\n")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("\naliasA: aliasB@srv-B\n")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("\n# Easy aliases.\npepe: jose\njoan: juan\n\n# UTF-8 aliases.\npitanga: ñangapirí\nañil: azul, índigo\n\n# Pipe aliases.\ntubo: | writemailto ../.data/pipe_alias_worked\n\n")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("")
|
||||
14
internal/aliases/testdata/fuzz/corpus/t-002
vendored
14
internal/aliases/testdata/fuzz/corpus/t-002
vendored
@@ -1,14 +0,0 @@
|
||||
# First some valid cases.
|
||||
a: b
|
||||
c: d@e, f,
|
||||
x: | command
|
||||
|
||||
# The following is invalid, should be ignored.
|
||||
a@dom: x@dom
|
||||
|
||||
# Overrides.
|
||||
o1: a
|
||||
o1: b
|
||||
|
||||
# Finally one to make the file NOT end in \n:
|
||||
y: z
|
||||
12
internal/aliases/testdata/fuzz/corpus/t-003
vendored
12
internal/aliases/testdata/fuzz/corpus/t-003
vendored
@@ -1,12 +0,0 @@
|
||||
|
||||
# Easy aliases.
|
||||
pepe: jose
|
||||
joan: juan
|
||||
|
||||
# UTF-8 aliases.
|
||||
pitanga: ñangapirí
|
||||
añil: azul, índigo
|
||||
|
||||
# Pipe aliases.
|
||||
tubo: | writemailto ../.data/pipe_alias_worked
|
||||
|
||||
3
internal/aliases/testdata/fuzz/corpus/t-004
vendored
3
internal/aliases/testdata/fuzz/corpus/t-004
vendored
@@ -1,3 +0,0 @@
|
||||
|
||||
fail: | false
|
||||
|
||||
2
internal/aliases/testdata/fuzz/corpus/t-005
vendored
2
internal/aliases/testdata/fuzz/corpus/t-005
vendored
@@ -1,2 +0,0 @@
|
||||
|
||||
aliasA: aliasB@srv-B
|
||||
@@ -285,3 +285,10 @@ func TestReload(t *testing.T) {
|
||||
t.Errorf("unexpected error reloading wrapped backend: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Fuzz testing for the response decoder, which handles user-provided data.
|
||||
func FuzzDecodeResponse(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, response string) {
|
||||
DecodeResponse(response)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// Fuzz testing for package aliases.
|
||||
|
||||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
package auth
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
// user, domain, passwd, err := DecodeResponse(string(data))
|
||||
interesting := 0
|
||||
_, _, _, err := DecodeResponse(string(data))
|
||||
if err == nil {
|
||||
interesting = 1
|
||||
}
|
||||
|
||||
return interesting
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("@\xed\x88̥̥̥̥̥̥̄̈́̈́̈́̈́̈́̈́̈́̈́ͥ̈́̈́ͥ̓\x00\x00")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("AHVAZABwYXNz")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("dUBkAHVAZABwYXNz")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("\xeb̥̥̥̥̥̥̥̈́ͥ̈́̈́̈́̈́̈́̈́̈́̈́̈́ͥ̓@\x00\x00")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("this is not base64 encoded")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("\xeb̥̥̥̥̥̥ͯ̈́̈́̈́̈́̈́ͯ̈́̈́̈́̈́̈́̈́@\x00\x00")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("\xeb̥̥̥̥̥̥̈́̈́̈́̈́̈́ͯ̈́̈́̈́̈́̈́̈́̈́@\x00\x00")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("dUBkAABwYXNz")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("w7FhY2FAw7FlcXVlAABjbGF2YXLDqQ==")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("dUBkAABwYXNz/w==")
|
||||
1
internal/auth/testdata/fuzz/corpus/t-001
vendored
1
internal/auth/testdata/fuzz/corpus/t-001
vendored
@@ -1 +0,0 @@
|
||||
dUBkAHVAZABwYXNz
|
||||
1
internal/auth/testdata/fuzz/corpus/t-002
vendored
1
internal/auth/testdata/fuzz/corpus/t-002
vendored
@@ -1 +0,0 @@
|
||||
dUBkAABwYXNz
|
||||
1
internal/auth/testdata/fuzz/corpus/t-003
vendored
1
internal/auth/testdata/fuzz/corpus/t-003
vendored
@@ -1 +0,0 @@
|
||||
AHVAZABwYXNz
|
||||
1
internal/auth/testdata/fuzz/corpus/t-004
vendored
1
internal/auth/testdata/fuzz/corpus/t-004
vendored
@@ -1 +0,0 @@
|
||||
dUBkAABwYXNz/w==
|
||||
1
internal/auth/testdata/fuzz/corpus/t-005
vendored
1
internal/auth/testdata/fuzz/corpus/t-005
vendored
@@ -1 +0,0 @@
|
||||
w7FhY2FAw7FlcXVlAABjbGF2YXLDqQ==
|
||||
1
internal/auth/testdata/fuzz/corpus/t-006
vendored
1
internal/auth/testdata/fuzz/corpus/t-006
vendored
@@ -1 +0,0 @@
|
||||
this is not base64 encoded
|
||||
BIN
internal/auth/testdata/fuzz/corpus/x-001
vendored
BIN
internal/auth/testdata/fuzz/corpus/x-001
vendored
Binary file not shown.
BIN
internal/auth/testdata/fuzz/corpus/x-002
vendored
BIN
internal/auth/testdata/fuzz/corpus/x-002
vendored
Binary file not shown.
BIN
internal/auth/testdata/fuzz/corpus/x-003
vendored
BIN
internal/auth/testdata/fuzz/corpus/x-003
vendored
Binary file not shown.
BIN
internal/auth/testdata/fuzz/corpus/x-004
vendored
BIN
internal/auth/testdata/fuzz/corpus/x-004
vendored
Binary file not shown.
@@ -1,16 +0,0 @@
|
||||
// Fuzz testing for package normalize.
|
||||
|
||||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
package normalize
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
s := string(data)
|
||||
User(s)
|
||||
Domain(s)
|
||||
Addr(s)
|
||||
DomainToUnicode(s)
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -128,3 +128,27 @@ func TestDomainToUnicode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzUser(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, user string) {
|
||||
User(user)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzDomain(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, domain string) {
|
||||
Domain(domain)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzAddr(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, addr string) {
|
||||
Addr(addr)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzDomainToUnicode(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, addr string) {
|
||||
DomainToUnicode(addr)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("ÑAndÚ")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("henryⅣ@throne")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("ñandú")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("pé@léa")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("Pingüino")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("henryⅣthrone")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("ÑAndÚ")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("péléa")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("ñandú")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("Pingüino")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("ÑAndÚ")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("henryⅣ@throne")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("ñandú")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("pé@léa")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("Pingüino")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("henryⅣthrone")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("ÑAndÚ")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("péléa")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("ñandú")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("Pingüino")
|
||||
@@ -1 +0,0 @@
|
||||
ñandú
|
||||
@@ -1 +0,0 @@
|
||||
ÑAndÚ
|
||||
@@ -1 +0,0 @@
|
||||
Pingüino
|
||||
@@ -1 +0,0 @@
|
||||
pé@léa
|
||||
@@ -1 +0,0 @@
|
||||
henryⅣ@throne
|
||||
@@ -3,6 +3,7 @@ package smtpsrv
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -15,7 +16,12 @@ import (
|
||||
func TestSecLevel(t *testing.T) {
|
||||
// We can't simulate this externally because of the SPF record
|
||||
// requirement, so do a narrow test on Conn.secLevelCheck.
|
||||
dir := testlib.MustTempDir(t)
|
||||
// Create the directory by hand because we don't want to automatically
|
||||
// chdir into it (it affects the fuzzing infrastructure).
|
||||
dir, err := os.MkdirTemp("", "testlib_")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v\n", dir)
|
||||
}
|
||||
defer testlib.RemoveIfOk(t, dir)
|
||||
|
||||
dinfo, err := domaininfo.New(dir)
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
// Fuzz testing for package smtpsrv. Based on server_test.
|
||||
|
||||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
package smtpsrv
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"blitiri.com.ar/go/chasquid/internal/aliases"
|
||||
"blitiri.com.ar/go/chasquid/internal/courier"
|
||||
"blitiri.com.ar/go/chasquid/internal/testlib"
|
||||
"blitiri.com.ar/go/chasquid/internal/userdb"
|
||||
"blitiri.com.ar/go/log"
|
||||
)
|
||||
|
||||
var (
|
||||
// Server addresses. Will be filled in at init time.
|
||||
smtpAddr = ""
|
||||
submissionAddr = ""
|
||||
submissionTLSAddr = ""
|
||||
|
||||
// TLS configuration to use in the clients.
|
||||
// Will contain the generated server certificate as root CA.
|
||||
tlsConfig *tls.Config
|
||||
)
|
||||
|
||||
//
|
||||
// === Fuzz test ===
|
||||
//
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
// Byte 0: mode
|
||||
// The rest is what we will send the server, one line per command.
|
||||
if len(data) < 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var mode SocketMode
|
||||
addr := ""
|
||||
switch data[0] {
|
||||
case '0':
|
||||
mode = ModeSMTP
|
||||
addr = smtpAddr
|
||||
case '1':
|
||||
mode = ModeSubmission
|
||||
addr = submissionAddr
|
||||
case '2':
|
||||
mode = ModeSubmissionTLS
|
||||
addr = submissionTLSAddr
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
data = data[1:]
|
||||
|
||||
var err error
|
||||
var conn net.Conn
|
||||
if mode.TLS {
|
||||
conn, err = tls.Dial("tcp", addr, tlsConfig)
|
||||
} else {
|
||||
conn, err = net.Dial("tcp", addr)
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to dial: %v", err))
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
tconn := textproto.NewConn(conn)
|
||||
defer tconn.Close()
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(data))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
cmd := strings.TrimSpace(strings.ToUpper(line))
|
||||
|
||||
// Skip STARTTLS if it happens on a non-TLS connection - the jump is
|
||||
// not going to happen via fuzzer, it will just cause a timeout (which
|
||||
// is considered a crash).
|
||||
if cmd == "STARTTLS" && !mode.TLS {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = tconn.PrintfLine(line); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if _, _, err = tconn.ReadResponse(-1); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if cmd == "DATA" {
|
||||
// We just sent DATA and got a response; send the contents.
|
||||
err = exchangeData(scanner, tconn)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err != nil && err != io.EOF) || scanner.Err() != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func exchangeData(scanner *bufio.Scanner, tconn *textproto.Conn) error {
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if err := tconn.PrintfLine(line); err != nil {
|
||||
return err
|
||||
}
|
||||
if line == "." {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Read the "." response.
|
||||
_, _, err := tconn.ReadResponse(-1)
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
// === Test environment ===
|
||||
//
|
||||
|
||||
// generateCert generates a new, INSECURE self-signed certificate and writes
|
||||
// it to a pair of (cert.pem, key.pem) files to the given path.
|
||||
// Note the certificate is only useful for testing purposes.
|
||||
func generateCert(path string) error {
|
||||
tmpl := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1234),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"chasquid_test.go"},
|
||||
},
|
||||
|
||||
DNSNames: []string{"localhost"},
|
||||
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
||||
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(24 * time.Hour),
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment |
|
||||
x509.KeyUsageDigitalSignature |
|
||||
x509.KeyUsageCertSign,
|
||||
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(
|
||||
rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a global config for convenience.
|
||||
srvCert, err := x509.ParseCertificate(derBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AddCert(srvCert)
|
||||
tlsConfig = &tls.Config{
|
||||
ServerName: "localhost",
|
||||
RootCAs: rootCAs,
|
||||
}
|
||||
|
||||
certOut, err := os.Create(path + "/cert.pem")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer certOut.Close()
|
||||
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
|
||||
keyOut, err := os.OpenFile(
|
||||
path+"/key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer keyOut.Close()
|
||||
|
||||
block := &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
||||
}
|
||||
pem.Encode(keyOut, block)
|
||||
return nil
|
||||
}
|
||||
|
||||
// waitForServer waits 10 seconds for the server to start, and returns an error
|
||||
// if it fails to do so.
|
||||
// It does this by repeatedly connecting to the address until it either
|
||||
// replies or times out. Note we do not do any validation of the reply.
|
||||
func waitForServer(addr string) {
|
||||
start := time.Now()
|
||||
for time.Since(start) < 10*time.Second {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("%v not reachable", addr))
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
|
||||
log.Default.Level = log.Error
|
||||
|
||||
// Generate certificates in a temporary directory.
|
||||
tmpDir, err := os.MkdirTemp("", "chasquid_smtpsrv_fuzz:")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Failed to create temp dir: %v\n", tmpDir))
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
err = generateCert(tmpDir)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Failed to generate cert for testing: %v\n", err))
|
||||
}
|
||||
|
||||
smtpAddr = testlib.GetFreePort()
|
||||
submissionAddr = testlib.GetFreePort()
|
||||
submissionTLSAddr = testlib.GetFreePort()
|
||||
|
||||
s := NewServer()
|
||||
s.Hostname = "localhost"
|
||||
s.MaxDataSize = 50 * 1024 * 1025
|
||||
s.AddCerts(tmpDir+"/cert.pem", tmpDir+"/key.pem")
|
||||
s.AddAddr(smtpAddr, ModeSMTP)
|
||||
s.AddAddr(submissionAddr, ModeSubmission)
|
||||
s.AddAddr(submissionTLSAddr, ModeSubmissionTLS)
|
||||
|
||||
localC := &courier.MDA{}
|
||||
remoteC := &courier.SMTP{}
|
||||
s.InitQueue(tmpDir+"/queue", localC, remoteC)
|
||||
s.InitDomainInfo(tmpDir + "/domaininfo")
|
||||
|
||||
udb := userdb.New("/dev/null")
|
||||
udb.AddUser("testuser", "testpasswd")
|
||||
s.aliasesR.AddAliasForTesting(
|
||||
"to@localhost", "testuser@localhost", aliases.EMAIL)
|
||||
s.AddDomain("localhost")
|
||||
s.AddUserDB("localhost", udb)
|
||||
|
||||
// Disable SPF lookups, to avoid leaking DNS queries.
|
||||
disableSPFForTesting = true
|
||||
|
||||
go s.ListenAndServe()
|
||||
|
||||
waitForServer(smtpAddr)
|
||||
waitForServer(submissionAddr)
|
||||
waitForServer(submissionTLSAddr)
|
||||
}
|
||||
96
internal/smtpsrv/fuzz_test.go
Normal file
96
internal/smtpsrv/fuzz_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Fuzz testing for package smtpsrv. Based on server_test.
|
||||
package smtpsrv
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func fuzzConnection(t *testing.T, modeI int, data []byte) {
|
||||
var mode SocketMode
|
||||
addr := ""
|
||||
switch modeI {
|
||||
case 0:
|
||||
mode = ModeSMTP
|
||||
addr = smtpAddr
|
||||
case 1:
|
||||
mode = ModeSubmission
|
||||
addr = submissionAddr
|
||||
case 2:
|
||||
mode = ModeSubmissionTLS
|
||||
addr = submissionTLSAddr
|
||||
default:
|
||||
mode = ModeSMTP
|
||||
addr = smtpAddr
|
||||
}
|
||||
|
||||
var err error
|
||||
var conn net.Conn
|
||||
if mode.TLS {
|
||||
conn, err = tls.Dial("tcp", addr, tlsConfig)
|
||||
} else {
|
||||
conn, err = net.Dial("tcp", addr)
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to dial: %v", err))
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
tconn := textproto.NewConn(conn)
|
||||
defer tconn.Close()
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(data))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
cmd := strings.TrimSpace(strings.ToUpper(line))
|
||||
|
||||
// Skip STARTTLS if it happens on a non-TLS connection - the jump is
|
||||
// not going to happen via fuzzer, it will just cause a timeout (which
|
||||
// is considered a crash).
|
||||
if cmd == "STARTTLS" && !mode.TLS {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = tconn.PrintfLine(line); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if _, _, err = tconn.ReadResponse(-1); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if cmd == "DATA" {
|
||||
// We just sent DATA and got a response; send the contents.
|
||||
err = exchangeData(scanner, tconn)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzConnection(f *testing.F) {
|
||||
f.Fuzz(fuzzConnection)
|
||||
}
|
||||
|
||||
func exchangeData(scanner *bufio.Scanner, tconn *textproto.Conn) error {
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if err := tconn.PrintfLine(line); err != nil {
|
||||
return err
|
||||
}
|
||||
if line == "." {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Read the "." response.
|
||||
_, _, err := tconn.ReadResponse(-1)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
int(0)
|
||||
[]byte("HELO localhost\nMAIL FROM:<test@testy.com>\nRCPT LALA: <>\nRCPT TO:\nRCPT TO:<pepe>\nRCPT TO:<a@xn--->\nRCPT TO:<henryⅣ@testserver>\nRCPT TO:<aaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaX@bbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbX>\n")
|
||||
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
int(0)
|
||||
[]byte("DATA\nHELO localhost\nDATA\nMAIL FROM:<a@b>\nRCPT TO: user@testserver\nDATA\nFrom: Mailer daemon <somewhere@horns.com>\nSubject: I've come to haunt you\nBad header\n\nMuahahahaha\n\n\n.\nQUIT\n")
|
||||
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
int(0)
|
||||
[]byte("EHLO localhost\nMAIL FROM: <>\nRCPT TO: user@testserver\nDATA\nFrom: Mailer daemon <somewhere@báratro>\nSubject: I've come to haunt you\nMessage-ID: <booooo>\n\nÑañañañaña!\n\n\n.\nQUIT\n")
|
||||
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
int(0)
|
||||
[]byte("HELO localhost\nMAIL LALA: <>\nMAIL FROM:\nMAIL FROM:<pepe>\nMAIL FROM:<a@xn--->\nMAIL FROM:<aaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaX@bbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbX>\n")
|
||||
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
int(0)
|
||||
[]byte("HELO\nEHLO\nHELO localhost\n")
|
||||
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
int(0)
|
||||
[]byte("HELO localhost\nQUIT\n")
|
||||
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
int(0)
|
||||
[]byte("EHLO localhost\nMAIL FROM: <>\nRCPT TO: user@testserver\nDATA\nFrom: Mailer daemon <somewhere@horns.com>\nSubject: I've come to haunt you\n\nMuahahahaha\n\n\n.\nQUIT\n")
|
||||
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
int(0)
|
||||
[]byte("EHLO localhost\nAUTH PLAIN something\nAUTH PLAIN something\nAUTH PLAIN something\nAUTH PLAIN something\nAUTH PLAIN something\n")
|
||||
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
int(0)
|
||||
[]byte("EHLO localhost\nWHATISTHIS\n")
|
||||
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
int(0)
|
||||
[]byte("EHLO localhost\nAUTH PLAIN\n")
|
||||
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
int(2)
|
||||
[]byte("EHLO localhost\nAUTH SOMETHINGELSE\nAUTH PLAIN\ndXNlckB0ZXN0c2VydmVyAHlalala==\nAUTH PLAIN\ndXNlckB0ZXN0c2VydmVyAHVzZXJAdGVzdHNlcnZlcgB3cm9uZ3Bhc3N3b3Jk\nAUTH PLAIN\ndXNlckB0ZXN0c2VydmVyAHVzZXJAdGVzdHNlcnZlcgBzZWNyZXRwYXNzd29yZA==\nAUTH PLAIN\n")
|
||||
@@ -1,9 +0,0 @@
|
||||
2EHLO localhost
|
||||
AUTH SOMETHINGELSE
|
||||
AUTH PLAIN
|
||||
dXNlckB0ZXN0c2VydmVyAHlalala==
|
||||
AUTH PLAIN
|
||||
dXNlckB0ZXN0c2VydmVyAHVzZXJAdGVzdHNlcnZlcgB3cm9uZ3Bhc3N3b3Jk
|
||||
AUTH PLAIN
|
||||
dXNlckB0ZXN0c2VydmVyAHVzZXJAdGVzdHNlcnZlcgBzZWNyZXRwYXNzd29yZA==
|
||||
AUTH PLAIN
|
||||
@@ -1,2 +0,0 @@
|
||||
0EHLO localhost
|
||||
AUTH PLAIN
|
||||
@@ -1,6 +0,0 @@
|
||||
0EHLO localhost
|
||||
AUTH PLAIN something
|
||||
AUTH PLAIN something
|
||||
AUTH PLAIN something
|
||||
AUTH PLAIN something
|
||||
AUTH PLAIN something
|
||||
15
internal/smtpsrv/testdata/fuzz/corpus/t-bad_data
vendored
15
internal/smtpsrv/testdata/fuzz/corpus/t-bad_data
vendored
@@ -1,15 +0,0 @@
|
||||
0DATA
|
||||
HELO localhost
|
||||
DATA
|
||||
MAIL FROM:<a@b>
|
||||
RCPT TO: user@testserver
|
||||
DATA
|
||||
From: Mailer daemon <somewhere@horns.com>
|
||||
Subject: I've come to haunt you
|
||||
Bad header
|
||||
|
||||
Muahahahaha
|
||||
|
||||
|
||||
.
|
||||
QUIT
|
||||
@@ -1,6 +0,0 @@
|
||||
0HELO localhost
|
||||
MAIL LALA: <>
|
||||
MAIL FROM:
|
||||
MAIL FROM:<pepe>
|
||||
MAIL FROM:<a@xn--->
|
||||
MAIL FROM:<aaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaX@bbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbX>
|
||||
@@ -1,8 +0,0 @@
|
||||
0HELO localhost
|
||||
MAIL FROM:<test@testy.com>
|
||||
RCPT LALA: <>
|
||||
RCPT TO:
|
||||
RCPT TO:<pepe>
|
||||
RCPT TO:<a@xn--->
|
||||
RCPT TO:<henryⅣ@testserver>
|
||||
RCPT TO:<aaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaX@bbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbX>
|
||||
@@ -1,3 +0,0 @@
|
||||
0HELO
|
||||
EHLO
|
||||
HELO localhost
|
||||
2
internal/smtpsrv/testdata/fuzz/corpus/t-helo
vendored
2
internal/smtpsrv/testdata/fuzz/corpus/t-helo
vendored
@@ -1,2 +0,0 @@
|
||||
0HELO localhost
|
||||
QUIT
|
||||
@@ -1,13 +0,0 @@
|
||||
0EHLO localhost
|
||||
MAIL FROM: <>
|
||||
RCPT TO: user@testserver
|
||||
DATA
|
||||
From: Mailer daemon <somewhere@báratro>
|
||||
Subject: I've come to haunt you
|
||||
Message-ID: <booooo>
|
||||
|
||||
Ñañañañaña!
|
||||
|
||||
|
||||
.
|
||||
QUIT
|
||||
12
internal/smtpsrv/testdata/fuzz/corpus/t-sendmail
vendored
12
internal/smtpsrv/testdata/fuzz/corpus/t-sendmail
vendored
@@ -1,12 +0,0 @@
|
||||
0EHLO localhost
|
||||
MAIL FROM: <>
|
||||
RCPT TO: user@testserver
|
||||
DATA
|
||||
From: Mailer daemon <somewhere@horns.com>
|
||||
Subject: I've come to haunt you
|
||||
|
||||
Muahahahaha
|
||||
|
||||
|
||||
.
|
||||
QUIT
|
||||
@@ -1,2 +0,0 @@
|
||||
0EHLO localhost
|
||||
WHATISTHIS
|
||||
Reference in New Issue
Block a user