crypt: Fix data corruption caused by seeking - #828

The corruption was caused when the file was read to the end thus
setting io.EOF and returning the buffers to the pool.  Seek reset the
EOF and carried on using the buffers that had been returned to the
pool thus causing corruption when other goroutines claimed the buffers
simultaneously.

Fix by resetting the buffer pointers to nil when released and claiming
new ones when seek resets EOF.  Also added locking for Read and Seek
which shouldn't be run concurrently.
This commit is contained in:
Nick Craig-Wood 2016-11-01 22:30:20 +00:00
parent cb40511807
commit 154e91bb23

View File

@ -8,7 +8,6 @@ import (
"encoding/base32" "encoding/base32"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"strings" "strings"
"sync" "sync"
"unicode/utf8" "unicode/utf8"
@ -48,6 +47,7 @@ var (
ErrorBadBase32Encoding = errors.New("bad base32 filename encoding") ErrorBadBase32Encoding = errors.New("bad base32 filename encoding")
ErrorFileClosed = errors.New("file already closed") ErrorFileClosed = errors.New("file already closed")
ErrorNotAnEncryptedFile = errors.New("not an encrypted file - no \"" + encryptedSuffix + "\" suffix") ErrorNotAnEncryptedFile = errors.New("not an encrypted file - no \"" + encryptedSuffix + "\" suffix")
ErrorBadSeek = errors.New("Seek beyond end of file")
defaultSalt = []byte{0xA8, 0x0D, 0xF4, 0x3A, 0x8F, 0xBD, 0x03, 0x08, 0xA7, 0xCA, 0xB8, 0x3E, 0x58, 0x1F, 0x86, 0xB1} defaultSalt = []byte{0xA8, 0x0D, 0xF4, 0x3A, 0x8F, 0xBD, 0x03, 0x08, 0xA7, 0xCA, 0xB8, 0x3E, 0x58, 0x1F, 0x86, 0xB1}
) )
@ -404,6 +404,7 @@ func (n *nonce) add(x uint64) {
// encrypter encrypts an io.Reader on the fly // encrypter encrypts an io.Reader on the fly
type encrypter struct { type encrypter struct {
mu sync.Mutex
in io.Reader in io.Reader
c *cipher c *cipher
nonce nonce nonce nonce
@ -437,6 +438,9 @@ func (c *cipher) newEncrypter(in io.Reader) (*encrypter, error) {
// Read as per io.Reader // Read as per io.Reader
func (fh *encrypter) Read(p []byte) (n int, err error) { func (fh *encrypter) Read(p []byte) (n int, err error) {
fh.mu.Lock()
defer fh.mu.Unlock()
if fh.err != nil { if fh.err != nil {
return 0, fh.err return 0, fh.err
} }
@ -474,7 +478,9 @@ func (fh *encrypter) finish(err error) (int, error) {
} }
fh.err = err fh.err = err
fh.c.putBlock(fh.buf) fh.c.putBlock(fh.buf)
fh.buf = nil
fh.c.putBlock(fh.readBuf) fh.c.putBlock(fh.readBuf)
fh.readBuf = nil
return 0, err return 0, err
} }
@ -489,6 +495,7 @@ func (c *cipher) EncryptData(in io.Reader) (io.Reader, error) {
// decrypter decrypts an io.ReaderCloser on the fly // decrypter decrypts an io.ReaderCloser on the fly
type decrypter struct { type decrypter struct {
mu sync.Mutex
rc io.ReadCloser rc io.ReadCloser
nonce nonce nonce nonce
initialNonce nonce initialNonce nonce
@ -551,37 +558,48 @@ func (c *cipher) newDecrypterSeek(open OpenAtOffset, offset int64) (fh *decrypte
return fh, nil return fh, nil
} }
// read data into internal buffer - call with fh.mu held
func (fh *decrypter) fillBuffer() (err error) {
// FIXME should overlap the reads with a go-routine and 2 buffers?
readBuf := fh.readBuf
n, err := io.ReadFull(fh.rc, readBuf)
if err == io.EOF {
// ReadFull only returns n=0 and EOF
return io.EOF
} else if err == io.ErrUnexpectedEOF {
// Next read will return EOF
} else if err != nil {
return err
}
// Check header + 1 byte exists
if n <= blockHeaderSize {
return ErrorEncryptedFileBadHeader
}
// Decrypt the block using the nonce
block := fh.buf
_, ok := secretbox.Open(block[:0], readBuf[:n], fh.nonce.pointer(), &fh.c.dataKey)
if !ok {
return ErrorEncryptedBadBlock
}
fh.bufIndex = 0
fh.bufSize = n - blockHeaderSize
fh.nonce.increment()
return nil
}
// Read as per io.Reader // Read as per io.Reader
func (fh *decrypter) Read(p []byte) (n int, err error) { func (fh *decrypter) Read(p []byte) (n int, err error) {
fh.mu.Lock()
defer fh.mu.Unlock()
if fh.err != nil { if fh.err != nil {
return 0, fh.err return 0, fh.err
} }
if fh.bufIndex >= fh.bufSize { if fh.bufIndex >= fh.bufSize {
// Read data err = fh.fillBuffer()
// FIXME should overlap the reads with a go-routine and 2 buffers? if err != nil {
readBuf := fh.readBuf
n, err = io.ReadFull(fh.rc, readBuf)
if err == io.EOF {
// ReadFull only returns n=0 and EOF
return 0, fh.finish(io.EOF)
} else if err == io.ErrUnexpectedEOF {
// Next read will return EOF
} else if err != nil {
return 0, fh.finish(err) return 0, fh.finish(err)
} }
// Check header + 1 byte exists
if n <= blockHeaderSize {
return 0, fh.finish(ErrorEncryptedFileBadHeader)
}
// Decrypt the block using the nonce
block := fh.buf
_, ok := secretbox.Open(block[:0], readBuf[:n], fh.nonce.pointer(), &fh.c.dataKey)
if !ok {
return 0, fh.finish(ErrorEncryptedBadBlock)
}
fh.bufIndex = 0
fh.bufSize = n - blockHeaderSize
fh.nonce.increment()
} }
n = copy(p, fh.buf[fh.bufIndex:fh.bufSize]) n = copy(p, fh.buf[fh.bufIndex:fh.bufSize])
fh.bufIndex += n fh.bufIndex += n
@ -590,6 +608,9 @@ func (fh *decrypter) Read(p []byte) (n int, err error) {
// Seek as per io.Seeker // Seek as per io.Seeker
func (fh *decrypter) Seek(offset int64, whence int) (int64, error) { func (fh *decrypter) Seek(offset int64, whence int) (int64, error) {
fh.mu.Lock()
defer fh.mu.Unlock()
if fh.open == nil { if fh.open == nil {
return 0, fh.finish(errors.New("can't seek - not initialised with newDecrypterSeek")) return 0, fh.finish(errors.New("can't seek - not initialised with newDecrypterSeek"))
} }
@ -599,7 +620,7 @@ func (fh *decrypter) Seek(offset int64, whence int) (int64, error) {
// Reset error or return it if not EOF // Reset error or return it if not EOF
if fh.err == io.EOF { if fh.err == io.EOF {
fh.err = nil fh.unFinish()
} else if fh.err != nil { } else if fh.err != nil {
return 0, fh.err return 0, fh.err
} }
@ -636,16 +657,18 @@ func (fh *decrypter) Seek(offset int64, whence int) (int64, error) {
fh.rc = rc fh.rc = rc
} }
// Empty the buffer // Fill the buffer
fh.bufIndex = 0 err := fh.fillBuffer()
fh.bufSize = 0
// Discard excess bytes
_, err := io.CopyN(ioutil.Discard, fh, discard)
if err != nil { if err != nil {
return 0, fh.finish(err) return 0, fh.finish(err)
} }
// Discard bytes from the buffer
if int(discard) > fh.bufSize {
return 0, fh.finish(ErrorBadSeek)
}
fh.bufIndex = int(discard)
return offset, nil return offset, nil
} }
@ -656,12 +679,31 @@ func (fh *decrypter) finish(err error) error {
} }
fh.err = err fh.err = err
fh.c.putBlock(fh.buf) fh.c.putBlock(fh.buf)
fh.buf = nil
fh.c.putBlock(fh.readBuf) fh.c.putBlock(fh.readBuf)
fh.readBuf = nil
return err return err
} }
// unFinish undoes the effects of finish
func (fh *decrypter) unFinish() {
// Clear error
fh.err = nil
// reinstate the buffers
fh.buf = fh.c.getBlock()
fh.readBuf = fh.c.getBlock()
// Empty the buffer
fh.bufIndex = 0
fh.bufSize = 0
}
// Close // Close
func (fh *decrypter) Close() error { func (fh *decrypter) Close() error {
fh.mu.Lock()
defer fh.mu.Unlock()
// Check already closed // Check already closed
if fh.err == ErrorFileClosed { if fh.err == ErrorFileClosed {
return fh.err return fh.err