diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/fusefrontend/args.go | 4 | ||||
| -rw-r--r-- | internal/fusefrontend/ctlsock_interface.go | 4 | ||||
| -rw-r--r-- | internal/fusefrontend/node_dir_ops.go | 111 | ||||
| -rw-r--r-- | internal/fusefrontend/node_prepare_syscall.go | 3 | ||||
| -rw-r--r-- | internal/fusefrontend/xattr_unit_test.go | 2 | ||||
| -rw-r--r-- | internal/nametransform/diriv.go | 22 | ||||
| -rw-r--r-- | internal/nametransform/longnames.go | 4 | ||||
| -rw-r--r-- | internal/nametransform/names.go | 14 | 
8 files changed, 95 insertions, 69 deletions
| diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go index 02ffddb..e20987b 100644 --- a/internal/fusefrontend/args.go +++ b/internal/fusefrontend/args.go @@ -53,6 +53,6 @@ type Args struct {  	// like rsync's `--one-file-system` does.  	// Only applicable to reverse mode.  	OneFileSystem bool -	// ZeroDirIV creates diriv files as all-zero files -	ZeroDirIV bool +	// DeterministicNames disables gocryptfs.diriv files +	DeterministicNames bool  } diff --git a/internal/fusefrontend/ctlsock_interface.go b/internal/fusefrontend/ctlsock_interface.go index 9e8cffc..87f0dc3 100644 --- a/internal/fusefrontend/ctlsock_interface.go +++ b/internal/fusefrontend/ctlsock_interface.go @@ -32,7 +32,7 @@ func (rn *RootNode) EncryptPath(plainPath string) (cipherPath string, err error)  	parts := strings.Split(plainPath, "/")  	wd := dirfd  	for i, part := range parts { -		dirIV, err := nametransform.ReadDirIVAt(wd) +		dirIV, err := rn.nameTransform.ReadDirIVAt(wd)  		if err != nil {  			return "", err  		} @@ -78,7 +78,7 @@ func (rn *RootNode) DecryptPath(cipherPath string) (plainPath string, err error)  	parts := strings.Split(cipherPath, "/")  	wd := dirfd  	for i, part := range parts { -		dirIV, err := nametransform.ReadDirIVAt(wd) +		dirIV, err := rn.nameTransform.ReadDirIVAt(wd)  		if err != nil {  			return "", err  		} diff --git a/internal/fusefrontend/node_dir_ops.go b/internal/fusefrontend/node_dir_ops.go index b43a4e4..c4ab861 100644 --- a/internal/fusefrontend/node_dir_ops.go +++ b/internal/fusefrontend/node_dir_ops.go @@ -34,9 +34,14 @@ func haveDsstore(entries []fuse.DirEntry) bool {  // mkdirWithIv - create a new directory and corresponding diriv file. dirfd  // should be a handle to the parent directory, cName is the name of the new  // directory and mode specifies the access permissions to use. +// If DeterministicNames is set, the diriv file is NOT created.  func (n *Node) mkdirWithIv(dirfd int, cName string, mode uint32, context *fuse.Context) error { -  	rn := n.rootNode() + +	if rn.args.DeterministicNames { +		return syscallcompat.MkdiratUser(dirfd, cName, mode, context) +	} +  	// Between the creation of the directory and the creation of gocryptfs.diriv  	// the directory is inconsistent. Take the lock to prevent other readers  	// from seeing it. @@ -49,7 +54,7 @@ func (n *Node) mkdirWithIv(dirfd int, cName string, mode uint32, context *fuse.C  	dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_DIRECTORY|syscall.O_NOFOLLOW|syscallcompat.O_PATH, 0)  	if err == nil {  		// Create gocryptfs.diriv -		err = nametransform.WriteDirIVAt(dirfd2, !rn.args.ZeroDirIV) +		err = nametransform.WriteDirIVAt(dirfd2)  		syscall.Close(dirfd2)  	}  	if err != nil { @@ -90,62 +95,67 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En  			return nil, fs.ToErrno(err)  		}  		st = syscallcompat.Unix2syscall(ust) -	} else { -		// We need write and execute permissions to create gocryptfs.diriv. -		// Also, we need read permissions to open the directory (to avoid -		// race-conditions between getting and setting the mode). -		origMode := mode -		mode = mode | 0700 -		// Handle long file name -		if nametransform.IsLongContent(cName) { -			// Create ".name" -			err := rn.nameTransform.WriteLongNameAt(dirfd, cName, name) -			if err != nil { -				return nil, fs.ToErrno(err) -			} +		// Create child node & return +		ch := n.newChild(ctx, &st, out) +		return ch, 0 -			// Create directory -			err = rn.mkdirWithIv(dirfd, cName, mode, context) -			if err != nil { -				nametransform.DeleteLongNameAt(dirfd, cName) -				return nil, fs.ToErrno(err) -			} -		} else { -			err := rn.mkdirWithIv(dirfd, cName, mode, context) -			if err != nil { -				return nil, fs.ToErrno(err) -			} -		} +	} + +	// We need write and execute permissions to create gocryptfs.diriv. +	// Also, we need read permissions to open the directory (to avoid +	// race-conditions between getting and setting the mode). +	origMode := mode +	mode = mode | 0700 -		fd, err := syscallcompat.Openat(dirfd, cName, -			syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) +	// Handle long file name +	if nametransform.IsLongContent(cName) { +		// Create ".name" +		err := rn.nameTransform.WriteLongNameAt(dirfd, cName, name)  		if err != nil { -			tlog.Warn.Printf("Mkdir %q: Openat failed: %v", cName, err)  			return nil, fs.ToErrno(err)  		} -		defer syscall.Close(fd) - -		err = syscall.Fstat(fd, &st) +		// Create directory & rollback .name file on error +		err = rn.mkdirWithIv(dirfd, cName, mode, context) +		if err != nil { +			nametransform.DeleteLongNameAt(dirfd, cName) +			return nil, fs.ToErrno(err) +		} +	} else { +		err := rn.mkdirWithIv(dirfd, cName, mode, context)  		if err != nil { -			tlog.Warn.Printf("Mkdir %q: Fstat failed: %v", cName, err)  			return nil, fs.ToErrno(err)  		} +	} -		// Fix permissions -		if origMode != mode { -			// Preserve SGID bit if it was set due to inheritance. -			origMode = uint32(st.Mode&^0777) | origMode -			err = syscall.Fchmod(fd, origMode) -			if err != nil { -				tlog.Warn.Printf("Mkdir %q: Fchmod %#o -> %#o failed: %v", cName, mode, origMode, err) -			} +	// Fill `st` +	fd, err := syscallcompat.Openat(dirfd, cName, +		syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) +	if err != nil { +		tlog.Warn.Printf("Mkdir %q: Openat failed: %v", cName, err) +		return nil, fs.ToErrno(err) +	} +	defer syscall.Close(fd) + +	err = syscall.Fstat(fd, &st) +	if err != nil { +		tlog.Warn.Printf("Mkdir %q: Fstat failed: %v", cName, err) +		return nil, fs.ToErrno(err) +	} + +	// Fix permissions +	if origMode != mode { +		// Preserve SGID bit if it was set due to inheritance. +		origMode = uint32(st.Mode&^0777) | origMode +		err = syscall.Fchmod(fd, origMode) +		if err != nil { +			tlog.Warn.Printf("Mkdir %q: Fchmod %#o -> %#o failed: %v", cName, mode, origMode, err)  		} +  	} -	// Create child node +	// Create child node & return  	ch := n.newChild(ctx, &st, out) -  	return ch, 0  } @@ -175,7 +185,7 @@ func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {  	rn := n.rootNode()  	if !rn.args.PlaintextNames {  		// Read the DirIV from disk -		cachedIV, err = nametransform.ReadDirIVAt(fd) +		cachedIV, err = rn.nameTransform.ReadDirIVAt(fd)  		if err != nil {  			tlog.Warn.Printf("OpenDir %q: could not read %s: %v", cDirName, nametransform.DirIVFilename, err)  			return nil, syscall.EIO @@ -196,7 +206,7 @@ func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {  			plain = append(plain, cipherEntries[i])  			continue  		} -		if cName == nametransform.DirIVFilename { +		if !rn.args.DeterministicNames && cName == nametransform.DirIVFilename {  			// silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled  			continue  		} @@ -249,6 +259,15 @@ func (n *Node) Rmdir(ctx context.Context, name string) (code syscall.Errno) {  		err := unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)  		return fs.ToErrno(err)  	} +	if rn.args.DeterministicNames { +		if err := unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR); err != nil { +			return fs.ToErrno(err) +		} +		if nametransform.IsLongContent(cName) { +			nametransform.DeleteLongNameAt(parentDirFd, cName) +		} +		return 0 +	}  	// Unless we are running as root, we need read, write and execute permissions  	// to handle gocryptfs.diriv.  	permWorkaround := false diff --git a/internal/fusefrontend/node_prepare_syscall.go b/internal/fusefrontend/node_prepare_syscall.go index 0894379..8a0e75c 100644 --- a/internal/fusefrontend/node_prepare_syscall.go +++ b/internal/fusefrontend/node_prepare_syscall.go @@ -8,7 +8,6 @@ import (  	"github.com/hanwen/go-fuse/v2/fs" -	"github.com/rfjakob/gocryptfs/internal/nametransform"  	"github.com/rfjakob/gocryptfs/internal/syscallcompat"  ) @@ -73,7 +72,7 @@ func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno sy  	// Cache store  	if !rn.args.PlaintextNames {  		var err error -		iv, err = nametransform.ReadDirIVAt(dirfd) +		iv, err = rn.nameTransform.ReadDirIVAt(dirfd)  		if err != nil {  			syscall.Close(dirfd)  			return -1, "", fs.ToErrno(err) diff --git a/internal/fusefrontend/xattr_unit_test.go b/internal/fusefrontend/xattr_unit_test.go index c48781c..397e3ef 100644 --- a/internal/fusefrontend/xattr_unit_test.go +++ b/internal/fusefrontend/xattr_unit_test.go @@ -19,7 +19,7 @@ func newTestFS(args Args) *RootNode {  	key := make([]byte, cryptocore.KeyLen)  	cCore := cryptocore.New(key, cryptocore.BackendGoGCM, contentenc.DefaultIVBits, true, false)  	cEnc := contentenc.New(cCore, contentenc.DefaultBS, false) -	n := nametransform.New(cCore.EMECipher, true, true, nil) +	n := nametransform.New(cCore.EMECipher, true, true, nil, false)  	rn := NewRootNode(args, cEnc, n)  	oneSec := time.Second  	options := &fs.Options{ diff --git a/internal/nametransform/diriv.go b/internal/nametransform/diriv.go index a288aa5..3a80baa 100644 --- a/internal/nametransform/diriv.go +++ b/internal/nametransform/diriv.go @@ -1,6 +1,7 @@  package nametransform  import ( +	"bytes"  	"fmt"  	"io"  	"os" @@ -22,7 +23,11 @@ const (  // ReadDirIVAt reads "gocryptfs.diriv" from the directory that is opened as "dirfd".  // Using the dirfd makes it immune to concurrent renames of the directory.  // Retries on EINTR. -func ReadDirIVAt(dirfd int) (iv []byte, err error) { +// If deterministicNames is set it returns an all-zero slice. +func (n *NameTransform) ReadDirIVAt(dirfd int) (iv []byte, err error) { +	if n.deterministicNames { +		return make([]byte, DirIVLen), nil +	}  	fdRaw, err := syscallcompat.Openat(dirfd, DirIVFilename,  		syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)  	if err != nil { @@ -33,6 +38,9 @@ func ReadDirIVAt(dirfd int) (iv []byte, err error) {  	return fdReadDirIV(fd)  } +// allZeroDirIV is preallocated to quickly check if the data read from disk is all zero +var allZeroDirIV = make([]byte, DirIVLen) +  // fdReadDirIV reads and verifies the DirIV from an opened gocryptfs.diriv file.  func fdReadDirIV(fd *os.File) (iv []byte, err error) {  	// We want to detect if the file is bigger than DirIVLen, so @@ -46,6 +54,9 @@ func fdReadDirIV(fd *os.File) (iv []byte, err error) {  	if len(iv) != DirIVLen {  		return nil, fmt.Errorf("wanted %d bytes, got %d", DirIVLen, len(iv))  	} +	if bytes.Equal(iv, allZeroDirIV) { +		return nil, fmt.Errorf("diriv is all-zero") +	}  	return iv, nil  } @@ -53,13 +64,8 @@ func fdReadDirIV(fd *os.File) (iv []byte, err error) {  // "dirfd". On error we try to delete the incomplete file.  // This function is exported because it is used from fusefrontend, main,  // and also the automated tests. -func WriteDirIVAt(dirfd int, randomInitialization bool) error { -	var iv []byte -	if randomInitialization { -		iv = cryptocore.RandBytes(DirIVLen) -	} else { -		iv = make([]byte, DirIVLen) -	} +func WriteDirIVAt(dirfd int) error { +	iv := cryptocore.RandBytes(DirIVLen)  	// 0400 permissions: gocryptfs.diriv should never be modified after creation.  	// Don't use "ioutil.WriteFile", it causes trouble on NFS:  	// https://github.com/rfjakob/gocryptfs/commit/7d38f80a78644c8ec4900cc990bfb894387112ed diff --git a/internal/nametransform/longnames.go b/internal/nametransform/longnames.go index 74ddb07..bf8060b 100644 --- a/internal/nametransform/longnames.go +++ b/internal/nametransform/longnames.go @@ -114,7 +114,7 @@ func ReadLongNameAt(dirfd int, cName string) (string, error) {  func DeleteLongNameAt(dirfd int, hashName string) error {  	err := syscallcompat.Unlinkat(dirfd, hashName+LongNameSuffix, 0)  	if err != nil { -		tlog.Warn.Printf("DeleteLongName: %v", err) +		tlog.Warn.Printf("DeleteLongNameAt: %v", err)  	}  	return err  } @@ -128,7 +128,7 @@ func (n *NameTransform) WriteLongNameAt(dirfd int, hashName string, plainName st  	plainName = filepath.Base(plainName)  	// Encrypt the basename -	dirIV, err := ReadDirIVAt(dirfd) +	dirIV, err := n.ReadDirIVAt(dirfd)  	if err != nil {  		return err  	} diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go index 566f0c7..412ccc0 100644 --- a/internal/nametransform/names.go +++ b/internal/nametransform/names.go @@ -25,11 +25,12 @@ type NameTransform struct {  	// on the Raw64 feature flag  	B64 *base64.Encoding  	// Patterns to bypass decryption -	badnamePatterns []string +	badnamePatterns    []string +	deterministicNames bool  }  // New returns a new NameTransform instance. -func New(e *eme.EMECipher, longNames bool, raw64 bool, badname []string) *NameTransform { +func New(e *eme.EMECipher, longNames bool, raw64 bool, badname []string, deterministicNames bool) *NameTransform {  	tlog.Debug.Printf("nametransform.New: longNames=%v, raw64=%v, badname=%q",  		longNames, raw64, badname) @@ -38,10 +39,11 @@ func New(e *eme.EMECipher, longNames bool, raw64 bool, badname []string) *NameTr  		b64 = base64.RawURLEncoding  	}  	return &NameTransform{ -		emeCipher:       e, -		longNames:       longNames, -		B64:             b64, -		badnamePatterns: badname, +		emeCipher:          e, +		longNames:          longNames, +		B64:                b64, +		badnamePatterns:    badname, +		deterministicNames: deterministicNames,  	}  } | 
