From c09bf1f2284706232642431c75fa1f3d8500a9d0 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Mon, 1 Oct 2018 21:28:54 +0200 Subject: fusefrontend: make DecryptPath() symlink-safe DecryptPath is now symlink-safe through the use of *at() functions. --- internal/fusefrontend/ctlsock_interface.go | 39 ++++++++++++++++++++++++------ internal/fusefrontend/fs_dir.go | 2 +- internal/nametransform/longnames.go | 18 ++++++++------ 3 files changed, 44 insertions(+), 15 deletions(-) (limited to 'internal') diff --git a/internal/fusefrontend/ctlsock_interface.go b/internal/fusefrontend/ctlsock_interface.go index 964775b..730ed58 100644 --- a/internal/fusefrontend/ctlsock_interface.go +++ b/internal/fusefrontend/ctlsock_interface.go @@ -4,9 +4,11 @@ import ( "fmt" "path" "strings" + "syscall" "github.com/rfjakob/gocryptfs/internal/ctlsock" "github.com/rfjakob/gocryptfs/internal/nametransform" + "github.com/rfjakob/gocryptfs/internal/syscallcompat" ) var _ ctlsock.Interface = &FS{} // Verify that interface is implemented. @@ -17,22 +19,31 @@ func (fs *FS) EncryptPath(plainPath string) (string, error) { } // DecryptPath implements ctlsock.Backend -func (fs *FS) DecryptPath(cipherPath string) (string, error) { +func (fs *FS) DecryptPath(cipherPath string) (plainPath string, err error) { + dirfd, err := syscall.Open(fs.args.Cipherdir, syscall.O_RDONLY, 0) + if err != nil { + return "", err + } + defer syscall.Close(dirfd) + return fs.decryptPathAt(dirfd, cipherPath) +} + +// decryptPathAt decrypts a ciphertext path relative to dirfd. +func (fs *FS) decryptPathAt(dirfd int, cipherPath string) (plainPath string, err error) { if fs.args.PlaintextNames || cipherPath == "" { return cipherPath, nil } - plainPath := "" parts := strings.Split(cipherPath, "/") - wd := fs.args.Cipherdir - for _, part := range parts { - dirIV, err := nametransform.ReadDirIV(wd) + wd := dirfd + for i, part := range parts { + dirIV, err := nametransform.ReadDirIVAt(wd) if err != nil { fmt.Printf("ReadDirIV: %v\n", err) return "", err } longPart := part if nametransform.IsLongContent(part) { - longPart, err = nametransform.ReadLongName(wd + "/" + part) + longPart, err = nametransform.ReadLongNameAt(wd, part) if err != nil { fmt.Printf("ReadLongName: %v\n", err) return "", err @@ -44,7 +55,21 @@ func (fs *FS) DecryptPath(cipherPath string) (string, error) { return "", err } plainPath = path.Join(plainPath, name) - wd = path.Join(wd, part) + // Last path component? We are done. + if i == len(parts)-1 { + break + } + // Descend into next directory + oldWd := wd + wd, err = syscallcompat.Openat(wd, part, syscall.O_NOFOLLOW, 0) + if err != nil { + return "", err + } + // Unless we are in the first iteration, where dirfd is our wd, close + // the old working directory. + if i > 0 { + syscall.Close(oldWd) + } } return plainPath, nil } diff --git a/internal/fusefrontend/fs_dir.go b/internal/fusefrontend/fs_dir.go index 963a551..76dff8e 100644 --- a/internal/fusefrontend/fs_dir.go +++ b/internal/fusefrontend/fs_dir.go @@ -324,7 +324,7 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f isLong = nametransform.NameType(cName) } if isLong == nametransform.LongNameContent { - cNameLong, err := nametransform.ReadLongName(filepath.Join(cDirAbsPath, cName)) + cNameLong, err := nametransform.ReadLongNameAt(fd, cName) if err != nil { tlog.Warn.Printf("OpenDir %q: invalid entry %q: Could not read .name: %v", cDirName, cName, err) diff --git a/internal/nametransform/longnames.go b/internal/nametransform/longnames.go index da18ebb..9c8637e 100644 --- a/internal/nametransform/longnames.go +++ b/internal/nametransform/longnames.go @@ -64,18 +64,18 @@ func IsLongContent(cName string) bool { } // ReadLongName - read "$path.name" -func ReadLongName(path string) (string, error) { - path += LongNameSuffix - fd, err := os.Open(path) +func ReadLongNameAt(dirfd int, cName string) (string, error) { + cName += LongNameSuffix + fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_NOFOLLOW, 0) if err != nil { return "", err } - defer fd.Close() + defer syscall.Close(fd) // 256 (=255 padded to 16) bytes base64-encoded take 344 bytes: "AAAAAAA...AAA==" lim := 344 // Allocate a bigger buffer so we see whether the file is too big buf := make([]byte, lim+1) - n, err := fd.ReadAt(buf, 0) + n, err := syscall.Pread(fd, buf, 0) if err != nil && err != io.EOF { return "", err } @@ -88,7 +88,9 @@ func ReadLongName(path string) (string, error) { return string(buf[0:n]), nil } -// DeleteLongName deletes "hashName.name". +// DeleteLongName deletes "hashName.name" in the directory openend at "dirfd". +// +// This function is symlink-safe through the use of Unlinkat(). func DeleteLongName(dirfd int, hashName string) error { err := syscallcompat.Unlinkat(dirfd, hashName+LongNameSuffix, 0) if err != nil { @@ -99,7 +101,9 @@ func DeleteLongName(dirfd int, hashName string) error { // WriteLongName encrypts plainName and writes it into "hashName.name". // For the convenience of the caller, plainName may also be a path and will be -// converted internally. +// Base()named internally. +// +// This function is symlink-safe through the use of Openat(). func (n *NameTransform) WriteLongName(dirfd int, hashName string, plainName string) (err error) { plainName = filepath.Base(plainName) -- cgit v1.2.3