diff --git a/internal/fusefrontend/ctlsock_interface.go b/internal/fusefrontend/ctlsock_interface.go index b29d150..15345f3 100644 --- a/internal/fusefrontend/ctlsock_interface.go +++ b/internal/fusefrontend/ctlsock_interface.go @@ -3,21 +3,45 @@ package fusefrontend import ( "fmt" "path" + "path/filepath" "strings" "syscall" "github.com/rfjakob/gocryptfs/internal/ctlsock" "github.com/rfjakob/gocryptfs/internal/nametransform" "github.com/rfjakob/gocryptfs/internal/syscallcompat" + "github.com/rfjakob/gocryptfs/internal/tlog" ) var _ ctlsock.Interface = &FS{} // Verify that interface is implemented. // EncryptPath implements ctlsock.Backend // -// TODO: this function is NOT symlink-safe. +// Symlink-safe through openBackingDir(). func (fs *FS) EncryptPath(plainPath string) (string, error) { - return fs.encryptPath(plainPath) + if plainPath == "" { + // Empty string gets encrypted as empty string + return plainPath, nil + } + if fs.args.PlaintextNames { + return plainPath, nil + } + // Encrypt path level by level using openBackingDir. Pretty inefficient, + // but does not matter here. + parts := strings.Split(plainPath, "/") + wd := "" + cPath := "" + for _, part := range parts { + wd = filepath.Join(wd, part) + dirfd, cName, err := fs.openBackingDir(wd) + if err != nil { + return "", err + } + syscall.Close(dirfd) + cPath = filepath.Join(cPath, cName) + } + tlog.Debug.Printf("encryptPath '%s' -> '%s'", plainPath, cPath) + return cPath, nil } // DecryptPath implements ctlsock.Backend diff --git a/internal/fusefrontend/names.go b/internal/fusefrontend/names.go index 36185e2..63f2e84 100644 --- a/internal/fusefrontend/names.go +++ b/internal/fusefrontend/names.go @@ -83,24 +83,3 @@ func (fs *FS) openBackingDir(relPath string) (dirfd int, cName string, err error } return dirfd, cName, nil } - -// encryptPath - encrypt relative plaintext path -// -// TODO: this function is NOT symlink-safe because EncryptPathDirIV is not -// symlink-safe. -func (fs *FS) encryptPath(plainPath string) (string, error) { - if plainPath != "" { // Empty path gets encrypted all the time without actual file accesses. - fs.AccessedSinceLastCheck = 1 - } else { // Empty string gets encrypted as empty string - return plainPath, nil - } - if fs.args.PlaintextNames { - return plainPath, nil - } - - fs.dirIVLock.RLock() - cPath, err := fs.nameTransform.EncryptPathDirIV(plainPath, fs.args.Cipherdir) - tlog.Debug.Printf("encryptPath '%s' -> '%s' (err: %v)", plainPath, cPath, err) - fs.dirIVLock.RUnlock() - return cPath, err -} diff --git a/internal/nametransform/diriv.go b/internal/nametransform/diriv.go index b98de0c..93c4c68 100644 --- a/internal/nametransform/diriv.go +++ b/internal/nametransform/diriv.go @@ -25,26 +25,6 @@ const ( DirIVFilename = "gocryptfs.diriv" ) -// ReadDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path) -// This function is exported because it allows for an efficient readdir implementation. -// If the directory itself cannot be opened, a syscall error will be returned. -// Otherwise, a fmt.Errorf() error value is returned with the details. -// -// TODO: this function is not symlink-safe and should be deleted once the only -// remaining user, EncryptPathDirIV(), is gone. -func ReadDirIV(dir string) (iv []byte, err error) { - fd, err := os.Open(filepath.Join(dir, DirIVFilename)) - if err != nil { - // Note: getting errors here is normal because of concurrent deletes. - // Strip the useless annotation that os.Open has added and return - // the plain syscall error. The caller will log a nice message. - err2 := err.(*os.PathError) - return nil, err2.Err - } - defer fd.Close() - return fdReadDirIV(fd) -} - // ReadDirIVAt reads "gocryptfs.diriv" from the directory that is opened as "dirfd". // Using the dirfd makes it immune to concurrent renames of the directory. func ReadDirIVAt(dirfd int) (iv []byte, err error) { @@ -132,53 +112,6 @@ func (be *NameTransform) EncryptAndHashName(name string, iv []byte) string { return cName } -// EncryptPathDirIV - encrypt relative plaintext path "plainPath" using EME with -// DirIV. "rootDir" is the backing storage root directory. -// Components that are longer than 255 bytes are hashed if be.longnames == true. -// -// TODO: EncryptPathDirIV is NOT SAFE against symlink races. This function -// should eventually be deleted. -func (be *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (string, error) { - var err error - // Empty string means root directory - if plainPath == "" { - return plainPath, nil - } - // Reject names longer than 255 bytes. - baseName := filepath.Base(plainPath) - if len(baseName) > unix.NAME_MAX { - return "", syscall.ENAMETOOLONG - } - // If we have the iv and the encrypted directory name in the cache, we - // can skip the directory walk. This optimization yields a 10% improvement - // in the tar extract benchmark. - parentDir := Dir(plainPath) - if iv, cParentDir := be.DirIVCache.Lookup(parentDir); iv != nil { - cBaseName := be.EncryptAndHashName(baseName, iv) - return filepath.Join(cParentDir, cBaseName), nil - } - // We have to walk the directory tree, starting at the root directory. - // ciphertext working directory (relative path) - cipherWD := "" - // plaintext working directory (relative path) - plainWD := "" - plainNames := strings.Split(plainPath, "/") - for _, plainName := range plainNames { - iv, _ := be.DirIVCache.Lookup(plainWD) - if iv == nil { - iv, err = ReadDirIV(filepath.Join(rootDir, cipherWD)) - if err != nil { - return "", err - } - be.DirIVCache.Store(plainWD, iv, cipherWD) - } - cipherName := be.EncryptAndHashName(plainName, iv) - cipherWD = filepath.Join(cipherWD, cipherName) - plainWD = filepath.Join(plainWD, plainName) - } - return cipherWD, nil -} - // Dir is like filepath.Dir but returns "" instead of ".". func Dir(path string) string { d := filepath.Dir(path) diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go index 22e639a..33128b9 100644 --- a/internal/nametransform/names.go +++ b/internal/nametransform/names.go @@ -74,11 +74,10 @@ func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error } // EncryptName encrypts "plainName", returns a base64-encoded "cipherName64". -// Used internally by EncryptPathDirIV(). // The encryption is either CBC or EME, depending on "useEME". // -// This function is exported because fusefrontend needs access to the full (not hashed) -// name if longname is used. Otherwise you should use EncryptPathDirIV() +// This function is exported because in some cases, fusefrontend needs access +// to the full (not hashed) name if longname is used. func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 string) { bin := []byte(plainName) bin = pad16(bin)