diff options
author | Jakob Unterwurzacher | 2021-08-20 10:57:26 +0200 |
---|---|---|
committer | Jakob Unterwurzacher | 2021-08-20 10:58:42 +0200 |
commit | 195d9d18a90d88ff2cb0530d832c59d98934fd1f (patch) | |
tree | e226de2355cfdf3f2d3a26411b85a338f2a264ef /internal | |
parent | 8f94083a2114c3aef4bc0320065e0374c420ea4a (diff) |
Implement -deterministic-names: extended -zerodiriv
-deterministc-names uses all-zero dirivs but does not write
them to disk anymore.
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, } } |