From 352f3147c5f1260cf729692d945c87e210cb1ad8 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Wed, 2 Jan 2019 21:52:52 +0100 Subject: fusefrontend: move openBackingDir into its own file This function is in all fastpaths, will get a cache, and needs its own file. renamed: internal/fusefrontend/names.go -> internal/fusefrontend/openbackingdir.go renamed: internal/fusefrontend/names_test.go -> internal/fusefrontend/openbackingdir_test.go --- internal/fusefrontend/fs.go | 19 ++++ internal/fusefrontend/names.go | 85 --------------- internal/fusefrontend/names_test.go | 152 --------------------------- internal/fusefrontend/openbackingdir.go | 63 +++++++++++ internal/fusefrontend/openbackingdir_test.go | 152 +++++++++++++++++++++++++++ 5 files changed, 234 insertions(+), 237 deletions(-) delete mode 100644 internal/fusefrontend/names.go delete mode 100644 internal/fusefrontend/names_test.go create mode 100644 internal/fusefrontend/openbackingdir.go create mode 100644 internal/fusefrontend/openbackingdir_test.go (limited to 'internal') diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index 5c52a19..5adade6 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -16,6 +16,7 @@ import ( "github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/fuse/pathfs" + "github.com/rfjakob/gocryptfs/internal/configfile" "github.com/rfjakob/gocryptfs/internal/contentenc" "github.com/rfjakob/gocryptfs/internal/nametransform" "github.com/rfjakob/gocryptfs/internal/serialize_reads" @@ -661,3 +662,21 @@ func (fs *FS) reportMitigatedCorruption(item string) { return } } + +// isFiltered - check if plaintext "path" should be forbidden +// +// Prevents name clashes with internal files when file names are not encrypted +func (fs *FS) isFiltered(path string) bool { + if !fs.args.PlaintextNames { + return false + } + // gocryptfs.conf in the root directory is forbidden + if path == configfile.ConfDefaultName { + tlog.Info.Printf("The name /%s is reserved when -plaintextnames is used\n", + configfile.ConfDefaultName) + return true + } + // Note: gocryptfs.diriv is NOT forbidden because diriv and plaintextnames + // are exclusive + return false +} diff --git a/internal/fusefrontend/names.go b/internal/fusefrontend/names.go deleted file mode 100644 index 63f2e84..0000000 --- a/internal/fusefrontend/names.go +++ /dev/null @@ -1,85 +0,0 @@ -package fusefrontend - -// This file forwards file encryption operations to cryptfs - -import ( - "path/filepath" - "strings" - "syscall" - - "github.com/rfjakob/gocryptfs/internal/configfile" - "github.com/rfjakob/gocryptfs/internal/nametransform" - "github.com/rfjakob/gocryptfs/internal/syscallcompat" - "github.com/rfjakob/gocryptfs/internal/tlog" -) - -// isFiltered - check if plaintext "path" should be forbidden -// -// Prevents name clashes with internal files when file names are not encrypted -func (fs *FS) isFiltered(path string) bool { - if !fs.args.PlaintextNames { - return false - } - // gocryptfs.conf in the root directory is forbidden - if path == configfile.ConfDefaultName { - tlog.Info.Printf("The name /%s is reserved when -plaintextnames is used\n", - configfile.ConfDefaultName) - return true - } - // Note: gocryptfs.diriv is NOT forbidden because diriv and plaintextnames - // are exclusive - return false -} - -// openBackingDir opens the parent ciphertext directory of plaintext path -// "relPath" and returns the dirfd and the encrypted basename. -// -// The caller should then use Openat(dirfd, cName, ...) and friends. -// For convenience, if relPath is "", cName is going to be ".". -// -// openBackingDir is secure against symlink races by using Openat and -// ReadDirIVAt. -func (fs *FS) openBackingDir(relPath string) (dirfd int, cName string, err error) { - // With PlaintextNames, we don't need to read DirIVs. Easy. - if fs.args.PlaintextNames { - dir := nametransform.Dir(relPath) - dirfd, err = syscallcompat.OpenDirNofollow(fs.args.Cipherdir, dir) - if err != nil { - return -1, "", err - } - // If relPath is empty, cName is ".". - cName = filepath.Base(relPath) - return dirfd, cName, nil - } - // Open cipherdir (following symlinks) - dirfd, err = syscall.Open(fs.args.Cipherdir, syscall.O_RDONLY|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0) - if err != nil { - return -1, "", err - } - // If relPath is empty, cName is ".". - if relPath == "" { - return dirfd, ".", nil - } - // Walk the directory tree - parts := strings.Split(relPath, "/") - for i, name := range parts { - iv, err := nametransform.ReadDirIVAt(dirfd) - if err != nil { - syscall.Close(dirfd) - return -1, "", err - } - cName = fs.nameTransform.EncryptAndHashName(name, iv) - // Last part? We are done. - if i == len(parts)-1 { - break - } - // Not the last part? Descend into next directory. - dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0) - syscall.Close(dirfd) - if err != nil { - return -1, "", err - } - dirfd = dirfd2 - } - return dirfd, cName, nil -} diff --git a/internal/fusefrontend/names_test.go b/internal/fusefrontend/names_test.go deleted file mode 100644 index 8453e52..0000000 --- a/internal/fusefrontend/names_test.go +++ /dev/null @@ -1,152 +0,0 @@ -package fusefrontend - -import ( - "strings" - "syscall" - "testing" - - "golang.org/x/sys/unix" - - "github.com/rfjakob/gocryptfs/internal/syscallcompat" - "github.com/rfjakob/gocryptfs/tests/test_helpers" -) - -func TestOpenBackingDir(t *testing.T) { - cipherdir := test_helpers.InitFS(t) - args := Args{ - Cipherdir: cipherdir, - } - fs := newTestFS(args) - - code := fs.Mkdir("dir1", 0700, nil) - if !code.Ok() { - t.Fatal(code) - } - code = fs.Mkdir("dir1/dir2", 0700, nil) - if !code.Ok() { - t.Fatal(code) - } - - dirfd, cName, err := fs.openBackingDir("") - if err != nil { - t.Fatal(err) - } - if cName != "." { - t.Fatal("cName should be .") - } - err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) - if err != nil { - t.Error(err) - } - err = syscallcompat.Faccessat(dirfd, ".", unix.R_OK) - if err != nil { - t.Error(err) - } - syscall.Close(dirfd) - - dirfd, cName, err = fs.openBackingDir("dir1") - if err != nil { - t.Fatal(err) - } - if cName == "" { - t.Fatal("cName should not be empty") - } - err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) - if err != nil { - t.Error(err) - } - syscall.Close(dirfd) - - dirfd, cName, err = fs.openBackingDir("dir1/dir2") - if err != nil { - t.Fatal(err) - } - if cName == "" { - t.Fatal("cName should not be empty") - } - err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) - if err != nil { - t.Error(err) - } - syscall.Close(dirfd) - - n255 := strings.Repeat("n", 255) - path := "dir1/" + n255 - fs.Mkdir(path, 0700, nil) - dirfd, cName, err = fs.openBackingDir(path) - if err != nil { - t.Fatal(err) - } - if cName == "" { - t.Fatal("cName should not be empty") - } - if len(cName) >= 255 { - t.Fatalf("cName is too long: %q", cName) - } - err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) - if err != nil { - t.Error(err) - } - syscall.Close(dirfd) -} - -func TestOpenBackingDirPlaintextNames(t *testing.T) { - cipherdir := test_helpers.InitFS(t, "-plaintextnames") - args := Args{ - Cipherdir: cipherdir, - PlaintextNames: true, - } - fs := newTestFS(args) - - code := fs.Mkdir("dir1", 0700, nil) - if !code.Ok() { - t.Fatal(code) - } - code = fs.Mkdir("dir1/dir2", 0700, nil) - if !code.Ok() { - t.Fatal(code) - } - - dirfd, cName, err := fs.openBackingDir("") - if err != nil { - t.Fatal(err) - } - if cName != "." { - t.Fatal("cName should be .") - } - err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) - if err != nil { - t.Error(err) - } - err = syscallcompat.Faccessat(dirfd, ".", unix.R_OK) - if err != nil { - t.Error(err) - } - syscall.Close(dirfd) - - dirfd, cName, err = fs.openBackingDir("dir1") - if err != nil { - t.Fatal(err) - } - if cName != "dir1" { - t.Fatalf("wrong cName: %q", cName) - } - err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) - if err != nil { - t.Error(err) - } - syscall.Close(dirfd) - - dirfd, cName, err = fs.openBackingDir("dir1/dir2") - if err != nil { - t.Fatal(err) - } - if cName != "dir2" { - t.Fatalf("wrong cName: %q", cName) - } - err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) - if err != nil { - t.Error(err) - } - syscall.Close(dirfd) -} diff --git a/internal/fusefrontend/openbackingdir.go b/internal/fusefrontend/openbackingdir.go new file mode 100644 index 0000000..849a486 --- /dev/null +++ b/internal/fusefrontend/openbackingdir.go @@ -0,0 +1,63 @@ +package fusefrontend + +import ( + "path/filepath" + "strings" + "syscall" + + "github.com/rfjakob/gocryptfs/internal/nametransform" + "github.com/rfjakob/gocryptfs/internal/syscallcompat" +) + +// openBackingDir opens the parent ciphertext directory of plaintext path +// "relPath" and returns the dirfd and the encrypted basename. +// +// The caller should then use Openat(dirfd, cName, ...) and friends. +// For convenience, if relPath is "", cName is going to be ".". +// +// openBackingDir is secure against symlink races by using Openat and +// ReadDirIVAt. +func (fs *FS) openBackingDir(relPath string) (dirfd int, cName string, err error) { + // With PlaintextNames, we don't need to read DirIVs. Easy. + if fs.args.PlaintextNames { + dir := nametransform.Dir(relPath) + dirfd, err = syscallcompat.OpenDirNofollow(fs.args.Cipherdir, dir) + if err != nil { + return -1, "", err + } + // If relPath is empty, cName is ".". + cName = filepath.Base(relPath) + return dirfd, cName, nil + } + // Open cipherdir (following symlinks) + dirfd, err = syscall.Open(fs.args.Cipherdir, syscall.O_RDONLY|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0) + if err != nil { + return -1, "", err + } + // If relPath is empty, cName is ".". + if relPath == "" { + return dirfd, ".", nil + } + // Walk the directory tree + parts := strings.Split(relPath, "/") + for i, name := range parts { + iv, err := nametransform.ReadDirIVAt(dirfd) + if err != nil { + syscall.Close(dirfd) + return -1, "", err + } + cName = fs.nameTransform.EncryptAndHashName(name, iv) + // Last part? We are done. + if i == len(parts)-1 { + break + } + // Not the last part? Descend into next directory. + dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0) + syscall.Close(dirfd) + if err != nil { + return -1, "", err + } + dirfd = dirfd2 + } + return dirfd, cName, nil +} diff --git a/internal/fusefrontend/openbackingdir_test.go b/internal/fusefrontend/openbackingdir_test.go new file mode 100644 index 0000000..8453e52 --- /dev/null +++ b/internal/fusefrontend/openbackingdir_test.go @@ -0,0 +1,152 @@ +package fusefrontend + +import ( + "strings" + "syscall" + "testing" + + "golang.org/x/sys/unix" + + "github.com/rfjakob/gocryptfs/internal/syscallcompat" + "github.com/rfjakob/gocryptfs/tests/test_helpers" +) + +func TestOpenBackingDir(t *testing.T) { + cipherdir := test_helpers.InitFS(t) + args := Args{ + Cipherdir: cipherdir, + } + fs := newTestFS(args) + + code := fs.Mkdir("dir1", 0700, nil) + if !code.Ok() { + t.Fatal(code) + } + code = fs.Mkdir("dir1/dir2", 0700, nil) + if !code.Ok() { + t.Fatal(code) + } + + dirfd, cName, err := fs.openBackingDir("") + if err != nil { + t.Fatal(err) + } + if cName != "." { + t.Fatal("cName should be .") + } + err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) + if err != nil { + t.Error(err) + } + err = syscallcompat.Faccessat(dirfd, ".", unix.R_OK) + if err != nil { + t.Error(err) + } + syscall.Close(dirfd) + + dirfd, cName, err = fs.openBackingDir("dir1") + if err != nil { + t.Fatal(err) + } + if cName == "" { + t.Fatal("cName should not be empty") + } + err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) + if err != nil { + t.Error(err) + } + syscall.Close(dirfd) + + dirfd, cName, err = fs.openBackingDir("dir1/dir2") + if err != nil { + t.Fatal(err) + } + if cName == "" { + t.Fatal("cName should not be empty") + } + err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) + if err != nil { + t.Error(err) + } + syscall.Close(dirfd) + + n255 := strings.Repeat("n", 255) + path := "dir1/" + n255 + fs.Mkdir(path, 0700, nil) + dirfd, cName, err = fs.openBackingDir(path) + if err != nil { + t.Fatal(err) + } + if cName == "" { + t.Fatal("cName should not be empty") + } + if len(cName) >= 255 { + t.Fatalf("cName is too long: %q", cName) + } + err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) + if err != nil { + t.Error(err) + } + syscall.Close(dirfd) +} + +func TestOpenBackingDirPlaintextNames(t *testing.T) { + cipherdir := test_helpers.InitFS(t, "-plaintextnames") + args := Args{ + Cipherdir: cipherdir, + PlaintextNames: true, + } + fs := newTestFS(args) + + code := fs.Mkdir("dir1", 0700, nil) + if !code.Ok() { + t.Fatal(code) + } + code = fs.Mkdir("dir1/dir2", 0700, nil) + if !code.Ok() { + t.Fatal(code) + } + + dirfd, cName, err := fs.openBackingDir("") + if err != nil { + t.Fatal(err) + } + if cName != "." { + t.Fatal("cName should be .") + } + err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) + if err != nil { + t.Error(err) + } + err = syscallcompat.Faccessat(dirfd, ".", unix.R_OK) + if err != nil { + t.Error(err) + } + syscall.Close(dirfd) + + dirfd, cName, err = fs.openBackingDir("dir1") + if err != nil { + t.Fatal(err) + } + if cName != "dir1" { + t.Fatalf("wrong cName: %q", cName) + } + err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) + if err != nil { + t.Error(err) + } + syscall.Close(dirfd) + + dirfd, cName, err = fs.openBackingDir("dir1/dir2") + if err != nil { + t.Fatal(err) + } + if cName != "dir2" { + t.Fatalf("wrong cName: %q", cName) + } + err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK) + if err != nil { + t.Error(err) + } + syscall.Close(dirfd) +} -- cgit v1.2.3