diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go index 4029913..ddfb9dc 100644 --- a/internal/fusefrontend/args.go +++ b/internal/fusefrontend/args.go @@ -6,7 +6,9 @@ import ( // Args is a container for arguments that are passed from main() to fusefrontend type Args struct { - Masterkey []byte + Masterkey []byte + // Cipherdir is the backing storage directory (absolute path). + // For reverse mode, Cipherdir actually contains *plaintext* files. Cipherdir string CryptoBackend cryptocore.AEADTypeEnum PlaintextNames bool diff --git a/internal/fusefrontend_reverse/ino_map.go b/internal/fusefrontend_reverse/ino_map.go deleted file mode 100644 index dae8222..0000000 --- a/internal/fusefrontend_reverse/ino_map.go +++ /dev/null @@ -1,19 +0,0 @@ -package fusefrontend_reverse - -import ( - "sync/atomic" -) - -func newInoGen() *inoGenT { - var ino uint64 = 1 - return &inoGenT{&ino} -} - -type inoGenT struct { - ino *uint64 -} - -// Get the next inode counter value -func (i *inoGenT) next() uint64 { - return atomic.AddUint64(i.ino, 1) -} diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 18f7506..a94f448 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -5,7 +5,6 @@ import ( "log" "os" "path/filepath" - "sync" "syscall" "github.com/hanwen/go-fuse/fuse" @@ -20,12 +19,6 @@ import ( "github.com/rfjakob/gocryptfs/internal/tlog" ) -const ( - // virtualFileMode is the mode to use for virtual files (gocryptfs.diriv and gocryptfs.longname.*.name) - // they are always readable, as stated in func Access - virtualFileMode = syscall.S_IFREG | 0444 -) - // ReverseFS implements the pathfs.FileSystem interface and provides an // encrypted view of a plaintext directory. type ReverseFS struct { @@ -39,12 +32,6 @@ type ReverseFS struct { nameTransform *nametransform.NameTransform // Content encryption helper contentEnc *contentenc.ContentEnc - // Inode number generator - inoGen *inoGenT - // Maps backing files device+inode pairs to user-facing unique inode numbers - inoMap map[fusefrontend.DevInoStruct]uint64 - // Protects map access - inoMapLock sync.Mutex } var _ pathfs.FileSystem = &ReverseFS{} @@ -68,8 +55,6 @@ func NewFS(args fusefrontend.Args) *ReverseFS { args: args, nameTransform: nameTransform, contentEnc: contentEnc, - inoGen: newInoGen(), - inoMap: map[fusefrontend.DevInoStruct]uint64{}, } } @@ -102,7 +87,7 @@ func (rfs *ReverseFS) isNameFile(relPath string) bool { return fileType == nametransform.LongNameFilename } -// isTranslatedConfig returns true if the default config file name is in +// isTranslatedConfig returns true if the default config file name is in use // and the ciphertext path is "gocryptfs.conf". // "gocryptfs.conf" then maps to ".gocryptfs.reverse.conf" in the plaintext // directory. @@ -116,47 +101,22 @@ func (rfs *ReverseFS) isTranslatedConfig(relPath string) bool { return false } -func (rfs *ReverseFS) inoAwareStat(relPlainPath string) (*fuse.Attr, fuse.Status) { - absPath, err := rfs.abs(relPlainPath, nil) - if err != nil { - return nil, fuse.ToStatus(err) - } - var fi os.FileInfo - if relPlainPath == "" { - // Look through symlinks for the root dir - fi, err = os.Stat(absPath) - } else { - fi, err = os.Lstat(absPath) - } - if err != nil { - return nil, fuse.ToStatus(err) - } - st := fi.Sys().(*syscall.Stat_t) - // The file has hard links. We have to give it a stable inode number so - // tar or rsync can find them. - if fi.Mode().IsRegular() && st.Nlink > 1 { - di := fusefrontend.DevInoFromStat(st) - rfs.inoMapLock.Lock() - stableIno := rfs.inoMap[di] - if stableIno == 0 { - rfs.inoMap[di] = rfs.inoGen.next() - } - rfs.inoMapLock.Unlock() - st.Ino = stableIno - } else { - st.Ino = rfs.inoGen.next() - } - a := &fuse.Attr{} - a.FromStat(st) - return a, fuse.OK -} - // GetAttr - FUSE call +// "relPath" is the relative ciphertext path func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) { + // Handle "gocryptfs.conf" if rfs.isTranslatedConfig(relPath) { - return rfs.inoAwareStat(configfile.ConfReverseName) + absConfPath, _ := rfs.abs(configfile.ConfReverseName, nil) + var st syscall.Stat_t + err := syscall.Lstat(absConfPath, &st) + if err != nil { + return nil, fuse.ToStatus(err) + } + var a fuse.Attr + a.FromStat(&st) + return &a, fuse.OK } - // Handle virtual files + // Handle virtual files (gocryptfs.diriv, *.name) var f nodefs.File var status fuse.Status virtual := false @@ -177,15 +137,31 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr status = f.GetAttr(&a) return &a, status } - - cPath, err := rfs.decryptPath(relPath) + // Decrypt path to "plaintext relative path" + pRelPath, err := rfs.decryptPath(relPath) if err != nil { return nil, fuse.ToStatus(err) } - a, status := rfs.inoAwareStat(cPath) - if !status.Ok() { - return nil, status + absPath, _ := rfs.abs(pRelPath, nil) + // Stat the backing file + var st syscall.Stat_t + if relPath == "" { + // Look through symlinks for the root dir + err = syscall.Stat(absPath, &st) + } else { + err = syscall.Lstat(absPath, &st) } + if err != nil { + return nil, fuse.ToStatus(err) + } + // Instead of risking an inode number collision, we return an error. + if st.Ino > virtualInoBase { + tlog.Warn.Printf("GetAttr %q: backing file inode number %d crosses reserved space, max=%d. Returning EOVERFLOW.", + relPath, st.Ino, virtualInoBase) + return nil, fuse.ToStatus(syscall.EOVERFLOW) + } + var a fuse.Attr + a.FromStat(&st) // Calculate encrypted file size if a.IsRegular() { a.Size = rfs.contentEnc.PlainSizeToCipherSize(a.Size) @@ -200,7 +176,7 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr a.Size = uint64(len(linkTarget)) } - return a, fuse.OK + return &a, fuse.OK } // Access - FUSE call diff --git a/internal/fusefrontend_reverse/rpath.go b/internal/fusefrontend_reverse/rpath.go index 02f4e9a..5082d11 100644 --- a/internal/fusefrontend_reverse/rpath.go +++ b/internal/fusefrontend_reverse/rpath.go @@ -37,6 +37,11 @@ func derivePathIV(path string, purpose ivPurposeType) []byte { return hash[:nametransform.DirIVLen] } +// abs basically returns storage dir + "/" + relPath. +// It takes an error parameter so it can directly wrap decryptPath like this: +// a, err := rfs.abs(rfs.decryptPath(relPath)) +// abs never generates an error on its own. In other words, abs(p, nil) never +// fails. func (rfs *ReverseFS) abs(relPath string, err error) (string, error) { if err != nil { return "", err diff --git a/internal/fusefrontend_reverse/virtualfile.go b/internal/fusefrontend_reverse/virtualfile.go index 2bd9e88..00fa726 100644 --- a/internal/fusefrontend_reverse/virtualfile.go +++ b/internal/fusefrontend_reverse/virtualfile.go @@ -9,6 +9,18 @@ import ( "github.com/rfjakob/gocryptfs/internal/tlog" ) +const ( + // virtualFileMode is the mode to use for virtual files (gocryptfs.diriv and + // *.name). They are always readable, as stated in func Access + virtualFileMode = syscall.S_IFREG | 0444 + // virtualInoBase is the start of the inode number range that is used + // for virtual files. + // The value 10^19 is just below 2^60. A power of 10 has been chosen so the + // "ls -li" output (which is base-10) is easy to read. + // 10^19 is the largest power of 10 that is smaller than UINT64_MAX/2. + virtualInoBase = uint64(1000000000000000000) +) + func (rfs *ReverseFS) newDirIVFile(cRelPath string) (nodefs.File, fuse.Status) { cDir := saneDir(cRelPath) absDir, err := rfs.abs(rfs.decryptPath(cDir)) @@ -25,8 +37,6 @@ type virtualFile struct { content []byte // absolute path to a parent file parentFile string - // inode number - ino uint64 } // newVirtualFile creates a new in-memory file that does not have a representation @@ -38,7 +48,6 @@ func (rfs *ReverseFS) newVirtualFile(content []byte, parentFile string) (nodefs. File: nodefs.NewDefaultFile(), content: content, parentFile: parentFile, - ino: rfs.inoGen.next(), }, fuse.OK } @@ -62,7 +71,12 @@ func (f *virtualFile) GetAttr(a *fuse.Attr) fuse.Status { tlog.Debug.Printf("GetAttr: Lstat %q: %v\n", f.parentFile, err) return fuse.ToStatus(err) } - st.Ino = f.ino + if st.Ino > virtualInoBase { + tlog.Warn.Printf("virtualFile.GetAttr: parent file inode number %d crosses reserved space, max=%d. Returning EOVERFLOW.", + st.Ino, virtualInoBase) + return fuse.ToStatus(syscall.EOVERFLOW) + } + st.Ino = st.Ino + virtualInoBase st.Size = int64(len(f.content)) st.Mode = virtualFileMode st.Nlink = 1