author | Alberto Bertogli
<albertito@blitiri.com.ar> 2019-11-06 00:39:18 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2019-11-30 11:38:46 UTC |
parent | 933b9792205eb1c01634fea77a650187e076c02e |
internal/smtpsrv/fuzz.go | +265 | -0 |
internal/smtpsrv/testdata/fuzz/corpus/t-auth_multi_dialog | +9 | -0 |
internal/smtpsrv/testdata/fuzz/corpus/t-auth_not_tls | +2 | -0 |
internal/smtpsrv/testdata/fuzz/corpus/t-auth_too_many_failures | +6 | -0 |
internal/smtpsrv/testdata/fuzz/corpus/t-bad_data | +15 | -0 |
internal/smtpsrv/testdata/fuzz/corpus/t-bad_mail_from | +6 | -0 |
internal/smtpsrv/testdata/fuzz/corpus/t-bad_rcpt_to | +8 | -0 |
internal/smtpsrv/testdata/fuzz/corpus/t-empty_helo | +3 | -0 |
internal/smtpsrv/testdata/fuzz/corpus/t-helo | +2 | -0 |
internal/smtpsrv/testdata/fuzz/corpus/t-null_address | +13 | -0 |
internal/smtpsrv/testdata/fuzz/corpus/t-sendmail | +12 | -0 |
internal/smtpsrv/testdata/fuzz/corpus/t-unknown_command | +2 | -0 |
diff --git a/internal/smtpsrv/fuzz.go b/internal/smtpsrv/fuzz.go new file mode 100644 index 0000000..2412456 --- /dev/null +++ b/internal/smtpsrv/fuzz.go @@ -0,0 +1,265 @@ +// Fuzz testing for package smtpsrv. Based on server_test. + +// +build gofuzz + +package smtpsrv + +import ( + "bufio" + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "flag" + "fmt" + "io" + "io/ioutil" + "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() + + in_data := false + scanner := bufio.NewScanner(bytes.NewBuffer(data)) + for scanner.Scan() { + line := scanner.Text() + + // 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 strings.TrimSpace(strings.ToUpper(line)) == "STARTTLS" && !mode.TLS { + continue + } + + if err = tconn.PrintfLine(line); err != nil { + break + } + if in_data { + if line == "." { + in_data = false + } else { + continue + } + } + + if _, _, err = tconn.ReadResponse(-1); err != nil { + break + } + + in_data = strings.HasPrefix(strings.ToUpper(line), "DATA") + } + if (err != nil && err != io.EOF) || scanner.Err() != nil { + return 1 + } + + return 0 +} + +// +// === 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.Debug + + // Generate certificates in a temporary directory. + tmpDir, err := ioutil.TempDir("", "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.Procmail{} + 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) +} diff --git a/internal/smtpsrv/testdata/fuzz/corpus/t-auth_multi_dialog b/internal/smtpsrv/testdata/fuzz/corpus/t-auth_multi_dialog new file mode 100644 index 0000000..bcacbc1 --- /dev/null +++ b/internal/smtpsrv/testdata/fuzz/corpus/t-auth_multi_dialog @@ -0,0 +1,9 @@ +2EHLO localhost +AUTH SOMETHINGELSE +AUTH PLAIN +dXNlckB0ZXN0c2VydmVyAHlalala== +AUTH PLAIN +dXNlckB0ZXN0c2VydmVyAHVzZXJAdGVzdHNlcnZlcgB3cm9uZ3Bhc3N3b3Jk +AUTH PLAIN +dXNlckB0ZXN0c2VydmVyAHVzZXJAdGVzdHNlcnZlcgBzZWNyZXRwYXNzd29yZA== +AUTH PLAIN diff --git a/internal/smtpsrv/testdata/fuzz/corpus/t-auth_not_tls b/internal/smtpsrv/testdata/fuzz/corpus/t-auth_not_tls new file mode 100644 index 0000000..6d74550 --- /dev/null +++ b/internal/smtpsrv/testdata/fuzz/corpus/t-auth_not_tls @@ -0,0 +1,2 @@ +0EHLO localhost +AUTH PLAIN diff --git a/internal/smtpsrv/testdata/fuzz/corpus/t-auth_too_many_failures b/internal/smtpsrv/testdata/fuzz/corpus/t-auth_too_many_failures new file mode 100644 index 0000000..8dc038c --- /dev/null +++ b/internal/smtpsrv/testdata/fuzz/corpus/t-auth_too_many_failures @@ -0,0 +1,6 @@ +0EHLO localhost +AUTH PLAIN something +AUTH PLAIN something +AUTH PLAIN something +AUTH PLAIN something +AUTH PLAIN something diff --git a/internal/smtpsrv/testdata/fuzz/corpus/t-bad_data b/internal/smtpsrv/testdata/fuzz/corpus/t-bad_data new file mode 100644 index 0000000..b8c6fcf --- /dev/null +++ b/internal/smtpsrv/testdata/fuzz/corpus/t-bad_data @@ -0,0 +1,15 @@ +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 diff --git a/internal/smtpsrv/testdata/fuzz/corpus/t-bad_mail_from b/internal/smtpsrv/testdata/fuzz/corpus/t-bad_mail_from new file mode 100644 index 0000000..15898ac --- /dev/null +++ b/internal/smtpsrv/testdata/fuzz/corpus/t-bad_mail_from @@ -0,0 +1,6 @@ +0HELO localhost +MAIL LALA: <> +MAIL FROM: +MAIL FROM:<pepe> +MAIL FROM:<a@xn---> +MAIL FROM:<aaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaXaaaa5aaaaX@bbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbXbbbb5bbbbX> diff --git a/internal/smtpsrv/testdata/fuzz/corpus/t-bad_rcpt_to b/internal/smtpsrv/testdata/fuzz/corpus/t-bad_rcpt_to new file mode 100644 index 0000000..4072bbc --- /dev/null +++ b/internal/smtpsrv/testdata/fuzz/corpus/t-bad_rcpt_to @@ -0,0 +1,8 @@ +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> diff --git a/internal/smtpsrv/testdata/fuzz/corpus/t-empty_helo b/internal/smtpsrv/testdata/fuzz/corpus/t-empty_helo new file mode 100644 index 0000000..236adea --- /dev/null +++ b/internal/smtpsrv/testdata/fuzz/corpus/t-empty_helo @@ -0,0 +1,3 @@ +0HELO +EHLO +HELO localhost diff --git a/internal/smtpsrv/testdata/fuzz/corpus/t-helo b/internal/smtpsrv/testdata/fuzz/corpus/t-helo new file mode 100644 index 0000000..7a5ba57 --- /dev/null +++ b/internal/smtpsrv/testdata/fuzz/corpus/t-helo @@ -0,0 +1,2 @@ +0HELO localhost +QUIT diff --git a/internal/smtpsrv/testdata/fuzz/corpus/t-null_address b/internal/smtpsrv/testdata/fuzz/corpus/t-null_address new file mode 100644 index 0000000..1f28266 --- /dev/null +++ b/internal/smtpsrv/testdata/fuzz/corpus/t-null_address @@ -0,0 +1,13 @@ +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 diff --git a/internal/smtpsrv/testdata/fuzz/corpus/t-sendmail b/internal/smtpsrv/testdata/fuzz/corpus/t-sendmail new file mode 100644 index 0000000..3553cf9 --- /dev/null +++ b/internal/smtpsrv/testdata/fuzz/corpus/t-sendmail @@ -0,0 +1,12 @@ +0EHLO localhost +MAIL FROM: <> +RCPT TO: user@testserver +DATA +From: Mailer daemon <somewhere@horns.com> +Subject: I've come to haunt you + +Muahahahaha + + +. +QUIT diff --git a/internal/smtpsrv/testdata/fuzz/corpus/t-unknown_command b/internal/smtpsrv/testdata/fuzz/corpus/t-unknown_command new file mode 100644 index 0000000..a87adce --- /dev/null +++ b/internal/smtpsrv/testdata/fuzz/corpus/t-unknown_command @@ -0,0 +1,2 @@ +0EHLO localhost +WHATISTHIS