diff options
| -rw-r--r-- | internal/fusefrontend/args.go | 4 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/ino_map.go | 19 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rfs.go | 94 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rpath.go | 5 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/virtualfile.go | 22 | 
5 files changed, 61 insertions, 83 deletions
| 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 | 
