crypt: Fix data corruption on seek

This was caused by failing to reset the internal buffer on seek so old
data was read first before the new data.

The unit tests didn't detect this because they were reading to the end
of the file to check integrity and thus emptying the internal buffer.

Both code and unit tests were fixed up.
This commit is contained in:
Nick Craig-Wood 2016-10-25 15:15:44 +01:00
parent 54d99d6ab2
commit 9d2dd2c49a
2 changed files with 38 additions and 25 deletions

View File

@ -604,17 +604,6 @@ func (fh *decrypter) Seek(offset int64, whence int) (int64, error) {
return 0, fh.err
}
// Can we seek it directly?
if do, ok := fh.rc.(io.Seeker); ok {
_, err := do.Seek(offset, 0)
if err != nil {
return 0, fh.finish(err)
}
} else {
// if not reopen with seek
_ = fh.rc.Close() // close underlying file
fh.rc = nil
// blocks we need to seek, plus bytes we need to discard
blocks, discard := offset/blockDataSize, offset%blockDataSize
@ -625,6 +614,18 @@ func (fh *decrypter) Seek(offset int64, whence int) (int64, error) {
fh.nonce = fh.initialNonce
fh.nonce.add(uint64(blocks))
// Can we seek underlying stream directly?
if do, ok := fh.rc.(io.Seeker); ok {
// Seek underlying stream directly
_, err := do.Seek(underlyingOffset, 0)
if err != nil {
return 0, fh.finish(err)
}
} else {
// if not reopen with seek
_ = fh.rc.Close() // close underlying file
fh.rc = nil
// Re-open the underlying object with the offset given
rc, err := fh.open(underlyingOffset)
if err != nil {
@ -633,13 +634,17 @@ func (fh *decrypter) Seek(offset int64, whence int) (int64, error) {
// Set the file handle
fh.rc = rc
}
// Empty the buffer
fh.bufIndex = 0
fh.bufSize = 0
// Discard excess bytes
_, err = io.CopyN(ioutil.Discard, fh, discard)
_, err := io.CopyN(ioutil.Discard, fh, discard)
if err != nil {
return 0, fh.finish(err)
}
}
return offset, nil
}

View File

@ -881,15 +881,26 @@ func TestNewDecrypterSeek(t *testing.T) {
return ioutil.NopCloser(bytes.NewBuffer(ciphertext[int(underlyingOffset):])), nil
}
inBlock := make([]byte, 1024)
// Check the seek worked by reading a block and checking it
// against what it should be
check := func(rc ReadSeekCloser, offset int) {
n, err := io.ReadFull(rc, inBlock)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
require.NoError(t, err)
}
seekedDecrypted := inBlock[:n]
require.Equal(t, plaintext[offset:offset+n], seekedDecrypted)
}
// Now try decoding it with a open/seek
for _, offset := range trials {
rc, err := c.DecryptDataSeek(open, int64(offset))
assert.NoError(t, err)
seekedDecrypted, err := ioutil.ReadAll(rc)
assert.NoError(t, err)
assert.Equal(t, plaintext[offset:], seekedDecrypted)
check(rc, offset)
}
// Now try decoding it with a single open and lots of seeks
@ -898,10 +909,7 @@ func TestNewDecrypterSeek(t *testing.T) {
_, err := rc.Seek(int64(offset), 0)
assert.NoError(t, err)
seekedDecrypted, err := ioutil.ReadAll(rc)
assert.NoError(t, err)
assert.Equal(t, plaintext[offset:], seekedDecrypted)
check(rc, offset)
}
}