1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-17 14:37:02 +00:00

smtpsrv: Limit incoming line length and improve large message handling

Currently, there is no limit to incoming line length, so an evil client
could cause a memory exhaustion DoS by issuing very long lines.

This patch fixes the bug by limiting the size of the lines.

To do that, we replace the textproto.Conn with a pair of buffered reader
and writer, which simplify the code and allow for better and cleaner
control.

Thanks to Max Mazurov (fox.cpp@disroot.org) for finding and reporting
this issue.
This commit is contained in:
Alberto Bertogli
2019-12-01 01:30:36 +00:00
parent d7006d0e16
commit 99df5e7b57
4 changed files with 134 additions and 17 deletions

View File

@@ -43,6 +43,9 @@ var (
// TLS configuration to use in the clients.
// Will contain the generated server certificate as root CA.
tlsConfig *tls.Config
// Max data size, in MiB.
maxDataSizeMiB = 5
)
//
@@ -259,14 +262,69 @@ func TestRelayForbidden(t *testing.T) {
}
}
func simpleCmd(t *testing.T, c *smtp.Client, cmd string, expected int) {
var str1MiB string
func sendLargeEmail(tb testing.TB, c *smtp.Client, sizeMiB int) error {
tb.Helper()
if err := c.Mail("from@from"); err != nil {
tb.Fatalf("Mail: %v", err)
}
if err := c.Rcpt("to@localhost"); err != nil {
tb.Fatalf("Rcpt: %v", err)
}
w, err := c.Data()
if err != nil {
tb.Fatalf("Data: %v", err)
}
if _, err := w.Write([]byte("Subject: I ate too much\n\n")); err != nil {
tb.Fatalf("Data write: %v", err)
}
// Write the 1 MiB string sizeMiB times.
for i := 0; i < sizeMiB; i++ {
if _, err := w.Write([]byte(str1MiB)); err != nil {
tb.Fatalf("Data write: %v", err)
}
}
return w.Close()
}
func TestTooMuchData(t *testing.T) {
c := mustDial(t, ModeSMTP, true)
defer c.Close()
err := sendLargeEmail(t, c, maxDataSizeMiB-1)
if err != nil {
t.Errorf("Error sending large but ok email: %v", err)
}
// Repeat the test - we want to check that the limit applies to each
// message, not the entire connection.
err = sendLargeEmail(t, c, maxDataSizeMiB-1)
if err != nil {
t.Errorf("Error sending large but ok email: %v", err)
}
err = sendLargeEmail(t, c, maxDataSizeMiB+1)
if err == nil || err.Error() != "552 5.3.4 Message too big" {
t.Fatalf("Expected message too big, got: %v", err)
}
}
func simpleCmd(t *testing.T, c *smtp.Client, cmd string, expected int) string {
t.Helper()
if err := c.Text.PrintfLine(cmd); err != nil {
t.Fatalf("Failed to write %s: %v", cmd, err)
}
if _, _, err := c.Text.ReadResponse(expected); err != nil {
_, msg, err := c.Text.ReadResponse(expected)
if err != nil {
t.Errorf("Incorrect %s response: %v", cmd, err)
}
return msg
}
func TestSimpleCommands(t *testing.T) {
@@ -278,6 +336,20 @@ func TestSimpleCommands(t *testing.T) {
simpleCmd(t, c, "EXPN", 502)
}
func TestLongLines(t *testing.T) {
c := mustDial(t, ModeSMTP, false)
defer c.Close()
// Send a not-too-long line.
simpleCmd(t, c, fmt.Sprintf("%1000s", "x"), 500)
// Send a very long line, expect an error.
msg := simpleCmd(t, c, fmt.Sprintf("%1001s", "x"), 554)
if msg != "error reading command: line too long" {
t.Errorf("Expected 'line too long', got %v", msg)
}
}
func TestReset(t *testing.T) {
c := mustDial(t, ModeSMTP, false)
defer c.Close()
@@ -448,6 +520,13 @@ func waitForServer(addr string) error {
func realMain(m *testing.M) int {
flag.Parse()
// Create a 1MiB string, which the large message tests use.
buf := make([]byte, 1024*1024)
for i := 0; i < len(buf); i++ {
buf[i] = 'a'
}
str1MiB = string(buf)
if *externalSMTPAddr != "" {
smtpAddr = *externalSMTPAddr
submissionAddr = *externalSubmissionAddr
@@ -476,7 +555,7 @@ func realMain(m *testing.M) int {
s := NewServer()
s.Hostname = "localhost"
s.MaxDataSize = 50 * 1024 * 1025
s.MaxDataSize = int64(maxDataSizeMiB) * 1024 * 1024
s.AddCerts(tmpDir+"/cert.pem", tmpDir+"/key.pem")
s.AddAddr(smtpAddr, ModeSMTP)
s.AddAddr(submissionAddr, ModeSubmission)