mirror of
https://github.com/mjl-/mox.git
synced 2025-06-28 01:48:15 +03:00

Writing to a connection goes through the flate library to compress. That writes the compressed bytes to the underlying connection. But that underlying connection is wrapped to raise a panic with an i/o error instead of returning a normal error. Jumping out of flate leaves the internal state of the compressor in undefined state. So far so good. But as part of cleaning up the connection, we could try to flush output again. Specifically: If we were writing user data, we had switched from tracing of protocol data to tracing of user data, and we registered a defer that restored the tracing kind and flushed (to ensure data was traced at the right level). That flush would cause a write into the compressor again, which could panic with an out of bounds slice access due to its inconsistent internal state. This fix prevents that compressor panic in two ways: 1. We wrap the flate.Writer with a moxio.FlateWriter that keeps track of whether a panic came out of an operation on it. If so, any further operation raises the same panic. This prevents access to the inconsistent internal flate state entirely. 2. Once we raise an i/o error, we mark the connection as broken and that makes flushes a no-op.
83 lines
2.5 KiB
Go
83 lines
2.5 KiB
Go
package imapserver
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"io"
|
|
mathrand "math/rand/v2"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestCompress(t *testing.T) {
|
|
tc := start(t)
|
|
defer tc.close()
|
|
|
|
tc.client.Login("mjl@mox.example", password0)
|
|
|
|
tc.transactf("bad", "compress")
|
|
tc.transactf("bad", "compress bogus ")
|
|
tc.transactf("no", "compress bogus")
|
|
|
|
tc.client.CompressDeflate()
|
|
tc.transactf("no", "compress deflate") // Cannot have multiple.
|
|
tc.xcode("COMPRESSIONACTIVE")
|
|
|
|
tc.client.Select("inbox")
|
|
tc.transactf("ok", "append inbox (\\seen) {%d+}\r\n%s", len(exampleMsg), exampleMsg)
|
|
tc.transactf("ok", "noop")
|
|
tc.transactf("ok", "fetch 1 body.peek[1]")
|
|
}
|
|
|
|
func TestCompressStartTLS(t *testing.T) {
|
|
tc := start(t)
|
|
defer tc.close()
|
|
|
|
tc.client.Starttls(&tls.Config{InsecureSkipVerify: true})
|
|
tc.client.Login("mjl@mox.example", password0)
|
|
tc.client.CompressDeflate()
|
|
tc.client.Select("inbox")
|
|
tc.transactf("ok", "append inbox (\\seen) {%d+}\r\n%s", len(exampleMsg), exampleMsg)
|
|
tc.transactf("ok", "noop")
|
|
tc.transactf("ok", "fetch 1 body.peek[1]")
|
|
}
|
|
|
|
func TestCompressBreak(t *testing.T) {
|
|
// Close the client connection when the server is writing. That causes writes in
|
|
// the server to fail (panic), jumping out of the flate writer and leaving its
|
|
// state inconsistent. We must not call into the flate writer again because due to
|
|
// its broken internal state it may cause array out of bounds accesses.
|
|
|
|
tc := start(t)
|
|
defer tc.close()
|
|
|
|
msg := exampleMsg
|
|
// Add random data (so it is not compressible). Don't know why, but only
|
|
// reproducible with large writes. As if setting socket buffers had no effect.
|
|
buf := make([]byte, 64*1024)
|
|
_, err := io.ReadFull(mathrand.NewChaCha8([32]byte{}), buf)
|
|
tcheck(t, err, "read random")
|
|
text := base64.StdEncoding.EncodeToString(buf)
|
|
for len(text) > 0 {
|
|
n := min(78, len(text))
|
|
msg += text[:n] + "\r\n"
|
|
text = text[n:]
|
|
}
|
|
|
|
tc.client.Login("mjl@mox.example", password0)
|
|
tc.client.CompressDeflate()
|
|
tc.client.Select("inbox")
|
|
tc.transactf("ok", "append inbox (\\seen) {%d+}\r\n%s", len(msg), msg)
|
|
tc.transactf("ok", "noop")
|
|
|
|
// Write request. Close connection instead of reading data. Write will panic,
|
|
// coming through flate writer leaving its state inconsistent. Server must not try
|
|
// to Flush/Write again on flate writer or it may panic.
|
|
tc.client.Writelinef("x fetch 1 body.peek[1]")
|
|
|
|
// Close client connection and prevent cleanup from closing the client again.
|
|
time.Sleep(time.Second / 10)
|
|
tc.client = nil
|
|
tc.conn.Close() // Simulate client disappearing.
|
|
}
|