aboutsummaryrefslogtreecommitdiff
path: root/internal/fusefrontend/file.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/fusefrontend/file.go')
-rw-r--r--internal/fusefrontend/file.go46
1 files changed, 44 insertions, 2 deletions
diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go
index 64c6ca0..9195f00 100644
--- a/internal/fusefrontend/file.go
+++ b/internal/fusefrontend/file.go
@@ -14,6 +14,8 @@ import (
"sync"
"syscall"
+ "golang.org/x/sys/unix"
+
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
@@ -90,6 +92,13 @@ func (f *File) readFileID() ([]byte, error) {
// and not only the header. A header-only file will be considered empty.
// This makes File ID poisoning more difficult.
readLen := contentenc.HeaderLen + 1
+ if f.rootNode.args.SharedStorage {
+ // With -sharedstorage, we consider a header-only file as valid, because
+ // another gocryptfs process may have either:
+ // 1) just created the header, and not written further data yet.
+ // 2) truncated the file down to just the header.
+ readLen = contentenc.HeaderLen
+ }
buf := make([]byte, readLen)
n, err := f.fd.ReadAt(buf, 0)
if err != nil {
@@ -244,7 +253,23 @@ func (f *File) Read(ctx context.Context, buf []byte, off int64) (resultData fuse
tlog.Debug.Printf("ino%d: FUSE Read: offset=%d length=%d", f.qIno.Ino, off, len(buf))
out, errno := f.doRead(buf[:0], uint64(off), uint64(len(buf)))
if errno != 0 {
- return nil, errno
+ // With -sharedstorage, when we get a decryption error, we lock the
+ // byte range and try again.
+ if !(f.rootNode.args.SharedStorage && errno == syscall.EIO) {
+ return nil, errno
+ }
+ blocks := f.rootNode.contentEnc.ExplodePlainRange(uint64(off), uint64(len(buf)))
+ alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks)
+ if err := f.LockSharedStorage(unix.F_RDLCK, int64(alignedOffset), int64(alignedLength)); err != nil {
+ tlog.Warn.Printf("ino%d: FUSE Read: LockSharedStorage(F_RDLCK, %d, %d) failed: %v", f.qIno.Ino, alignedOffset, alignedLength, err)
+ return nil, fs.ToErrno(err)
+ }
+ defer f.UnlockSharedStorage(int64(alignedOffset), int64(alignedLength))
+
+ out, errno = f.doRead(buf[:0], uint64(off), uint64(len(buf)))
+ if errno != 0 {
+ return nil, errno
+ }
}
tlog.Debug.Printf("ino%d: Read: errno=%d, returning %d bytes", f.qIno.Ino, errno, len(out))
return fuse.ReadResultData(out), errno
@@ -266,6 +291,12 @@ func (f *File) doWrite(data []byte, off int64) (uint32, syscall.Errno) {
//
// If the file ID is not cached, read it from disk
if f.fileTableEntry.ID == nil {
+ if err := f.LockSharedStorage(unix.F_WRLCK, 0, contentenc.HeaderLen); err != nil {
+ tlog.Warn.Printf("ino%d: doWrite: LockSharedStorage(F_WRLCK, %d, %d) failed: %v", f.qIno.Ino, 0, contentenc.HeaderLen, err)
+ return 0, fs.ToErrno(err)
+ }
+ defer f.UnlockSharedStorage(0, contentenc.HeaderLen)
+
var err error
fileID, err := f.readFileID()
// Write a new file header if the file is empty
@@ -285,7 +316,19 @@ func (f *File) doWrite(data []byte, off int64) (uint32, syscall.Errno) {
// Handle payload data
dataBuf := bytes.NewBuffer(data)
blocks := f.rootNode.contentEnc.ExplodePlainRange(uint64(off), uint64(len(data)))
+ cOff, lkLen := blocks[0].JointCiphertextRange(blocks)
toEncrypt := make([][]byte, len(blocks))
+
+ // As we must write complete ciphertext blocks (except at EOF), non-overlapping
+ // plaintext writes can overlap in the ciphertext.
+ // And because overlapping writes can turn the data into data soup (see
+ // TestPoCTornWrite) we serialize them using fcntl locking.
+ if err := f.LockSharedStorage(unix.F_WRLCK, int64(cOff), int64(lkLen)); err != nil {
+ tlog.Warn.Printf("ino%d: LockSharedStorage(F_WRLCK, %d, %d) failed: %v", f.qIno.Ino, cOff, int64(lkLen), err)
+ return 0, fs.ToErrno(err)
+ }
+ defer f.UnlockSharedStorage(int64(cOff), int64(lkLen))
+
for i, b := range blocks {
blockData := dataBuf.Next(int(b.Length))
// Incomplete block -> Read-Modify-Write
@@ -310,7 +353,6 @@ func (f *File) doWrite(data []byte, off int64) (uint32, syscall.Errno) {
// Preallocate so we cannot run out of space in the middle of the write.
// This prevents partially written (=corrupt) blocks.
var err error
- cOff := blocks[0].BlockCipherOff()
// f.fd.WriteAt & syscallcompat.EnospcPrealloc take int64 offsets!
if cOff > math.MaxInt64 {
return 0, syscall.EFBIG