From c06682f7321f022a4c1e1bdec60ddc2de9bd2490 Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Sat, 16 Feb 2019 18:55:54 -0200 Subject: [PATCH 01/18] Decides exclusions based on plaintext paths. Since exclusions will support wildcards, it's not possible to store the list of exclusions as a list of encrypted paths (calculated during initialization). Instead, the plain paths are stored, and the path to be checked is decrypted at the time of isExcluded check and compared with the stored plaintext paths. --- internal/fusefrontend_reverse/rfs.go | 33 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 6ee10fc3..1f9e055b 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -68,21 +68,7 @@ func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.Na tlog.Fatal.Printf("-exclude: excluding the root dir %q makes no sense", clean) os.Exit(exitcodes.ExcludeError) } - cPath, err := fs.EncryptPath(clean) - if err != nil { - tlog.Fatal.Printf("-exclude: EncryptPath %q failed: %v", clean, err) - os.Exit(exitcodes.ExcludeError) - } - fs.cExclude = append(fs.cExclude, cPath) - if !fs.args.PlaintextNames { - // If we exclude - // gocryptfs.longname.3vZ_r3eDPb1_fL3j5VA4rd_bcKWLKT9eaxOVIGK5HFA - // we should also exclude - // gocryptfs.longname.3vZ_r3eDPb1_fL3j5VA4rd_bcKWLKT9eaxOVIGK5HFA.name - if nametransform.IsLongContent(filepath.Base(cPath)) { - fs.cExclude = append(fs.cExclude, cPath+nametransform.LongNameSuffix) - } - } + fs.cExclude = append(fs.cExclude, clean) } tlog.Debug.Printf("-exclude: %v -> %v", fs.args.Exclude, fs.cExclude) } @@ -103,17 +89,30 @@ func relDir(path string) string { // isExcluded finds out if relative ciphertext path "relPath" is excluded // (used when -exclude is passed by the user) func (rfs *ReverseFS) isExcluded(relPath string) bool { + if rfs.isTranslatedConfig(relPath) || rfs.isDirIV(relPath) { + return false + } + if rfs.isNameFile(relPath) { + relPath = relPath[:len(relPath)-len(nametransform.LongNameSuffix)] + } + decPath, err := rfs.decryptPath(relPath) + if err != nil { + if err != syscall.ENOENT { + tlog.Warn.Printf("Error decrypting path %q", relPath) + } + return false + } for _, e := range rfs.cExclude { // If the root dir is excluded, everything is excluded. if e == "" { return true } // This exact path is excluded - if e == relPath { + if e == decPath { return true } // Files inside an excluded directory are also excluded - if strings.HasPrefix(relPath, e+"/") { + if strings.HasPrefix(decPath, e+"/") { return true } } From bd9889ae987d0d283bcaaf6d7641f9492fc22ff3 Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Sun, 17 Feb 2019 07:03:43 -0300 Subject: [PATCH 02/18] Function to remove .name suffix from LongNameFilename. --- internal/fusefrontend_reverse/reverse_longnames.go | 2 +- internal/fusefrontend_reverse/rfs.go | 2 +- internal/nametransform/longnames.go | 7 +++++++ internal/nametransform/longnames_test.go | 8 ++++++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/internal/fusefrontend_reverse/reverse_longnames.go b/internal/fusefrontend_reverse/reverse_longnames.go index a425e64b..9f044e85 100644 --- a/internal/fusefrontend_reverse/reverse_longnames.go +++ b/internal/fusefrontend_reverse/reverse_longnames.go @@ -106,7 +106,7 @@ func (rfs *ReverseFS) findLongnameParent(dir string, dirIV []byte, longname stri func (rfs *ReverseFS) newNameFile(relPath string) (nodefs.File, fuse.Status) { dotName := filepath.Base(relPath) // gocryptfs.longname.XYZ.name - longname := dotName[:len(dotName)-len(nametransform.LongNameSuffix)] // gocryptfs.longname.XYZ + longname := nametransform.RemoveLongNameSuffix(dotName) // gocryptfs.longname.XYZ // cipher directory cDir := nametransform.Dir(relPath) // plain directory diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 1f9e055b..b0c34ef5 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -93,7 +93,7 @@ func (rfs *ReverseFS) isExcluded(relPath string) bool { return false } if rfs.isNameFile(relPath) { - relPath = relPath[:len(relPath)-len(nametransform.LongNameSuffix)] + relPath = nametransform.RemoveLongNameSuffix(relPath) } decPath, err := rfs.decryptPath(relPath) if err != nil { diff --git a/internal/nametransform/longnames.go b/internal/nametransform/longnames.go index 7a6d4137..1bbcbb61 100644 --- a/internal/nametransform/longnames.go +++ b/internal/nametransform/longnames.go @@ -69,6 +69,13 @@ func IsLongContent(cName string) bool { return NameType(cName) == LongNameContent } +// RemoveLongNameSuffix removes the ".name" suffix from cName, returning the corresponding +// content file name. +// No check is made if cName actually is a LongNameFilename. +func RemoveLongNameSuffix(cName string) string { + return cName[:len(cName)-len(LongNameSuffix)] +} + // ReadLongName - read cName + ".name" from the directory opened as dirfd. // // Symlink-safe through Openat(). diff --git a/internal/nametransform/longnames_test.go b/internal/nametransform/longnames_test.go index 8fa19fe6..42104922 100644 --- a/internal/nametransform/longnames_test.go +++ b/internal/nametransform/longnames_test.go @@ -20,3 +20,11 @@ func TestIsLongName(t *testing.T) { t.Errorf("False positive") } } + +func TestRemoveLongNameSuffix(t *testing.T) { + filename := "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=.name" + content := "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=" + if RemoveLongNameSuffix(filename) != content { + t.Error(".name suffix not removed") + } +} From b2aa2ddb9884b1e0f0778857f03bc447f4d76833 Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Sun, 17 Feb 2019 11:22:33 -0300 Subject: [PATCH 03/18] --exclude now supports gitignore-like patterns with wildcards. --- Gopkg.lock | 9 ++++ Gopkg.toml | 4 ++ .../fusefrontend_reverse/isexcluded_test.go | 54 ++++++++++++++----- internal/fusefrontend_reverse/rfs.go | 47 ++++++---------- tests/reverse/exclude_test.go | 50 +++++++++++++---- .../file => bkp1~} | 0 tests/reverse/exclude_test_fs/dir1/exclude | 0 ...xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx~ | 0 ...xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | 0 .../exclude_test_fs/dir1/subdir1/exclude | 0 .../dir1/subdir1/subdir2/exclude | 0 .../file1 | 0 .../bkp~ | 0 ...xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | 0 14 files changed, 108 insertions(+), 56 deletions(-) rename tests/reverse/exclude_test_fs/{longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file => bkp1~} (100%) create mode 100644 tests/reverse/exclude_test_fs/dir1/exclude create mode 100644 tests/reverse/exclude_test_fs/dir1/longbkp1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx~ create mode 100644 tests/reverse/exclude_test_fs/dir1/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx create mode 100644 tests/reverse/exclude_test_fs/dir1/subdir1/exclude create mode 100644 tests/reverse/exclude_test_fs/dir1/subdir1/subdir2/exclude create mode 100644 tests/reverse/exclude_test_fs/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file1 create mode 100644 tests/reverse/exclude_test_fs/longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/bkp~ create mode 100644 tests/reverse/exclude_test_fs/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx diff --git a/Gopkg.lock b/Gopkg.lock index 2979abf2..3a050294 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -43,6 +43,14 @@ pruneopts = "" revision = "2222dbd4ba467ab3fc7e8af41562fcfe69c0d770" +[[projects]] + branch = "master" + digest = "1:321aeb7694b1803b7c7e27191e464ec2ce541a2730b3a0c5f7451296d9b3a69e" + name = "github.com/sabhiram/go-gitignore" + packages = ["."] + pruneopts = "" + revision = "d3107576ba9425fc1c85f4b3569c4631b805a02e" + [[projects]] branch = "master" digest = "1:5462386895a322b94a79a5de7314310edbd5f4d15f851ae032f146b2c951b3de" @@ -85,6 +93,7 @@ "github.com/jacobsa/crypto/siv", "github.com/pkg/xattr", "github.com/rfjakob/eme", + "github.com/sabhiram/go-gitignore", "golang.org/x/crypto/hkdf", "golang.org/x/crypto/scrypt", "golang.org/x/crypto/ssh/terminal", diff --git a/Gopkg.toml b/Gopkg.toml index fafb8f39..fe55312b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -14,6 +14,10 @@ branch = "master" name = "github.com/rfjakob/eme" +[[constraint]] + branch = "master" + name = "github.com/sabhiram/go-gitignore" + [[constraint]] branch = "master" name = "golang.org/x/crypto" diff --git a/internal/fusefrontend_reverse/isexcluded_test.go b/internal/fusefrontend_reverse/isexcluded_test.go index fc3831aa..8c2dc2ea 100644 --- a/internal/fusefrontend_reverse/isexcluded_test.go +++ b/internal/fusefrontend_reverse/isexcluded_test.go @@ -2,25 +2,51 @@ package fusefrontend_reverse import ( "testing" + + "github.com/rfjakob/gocryptfs/internal/configfile" + "github.com/rfjakob/gocryptfs/internal/nametransform" ) -func verifyExcluded(t *testing.T, rfs *ReverseFS, paths []string) { - for _, p := range paths { - if !rfs.isExcluded(p) { - t.Errorf("Path %q should be excluded, but is not", p) - } - } - if t.Failed() { - t.Logf("cExclude = %#v", rfs.cExclude) - } +type IgnoreParserMock struct { + calledWith string +} + +func (parser *IgnoreParserMock) MatchesPath(f string) bool { + parser.calledWith = f + return false } // Note: See also the integration tests in // tests/reverse/exclude_test.go -func TestIsExcluded(t *testing.T) { +func TestShouldReturnFalseIfThereAreNoExclusions(t *testing.T) { + var rfs ReverseFS + if rfs.isExcluded("any/path") { + t.Error("Should not exclude any path is no exclusions were specified") + } +} + +func TestShouldNoCallIgnoreParserForTranslatedConfig(t *testing.T) { + ignorerMock := &IgnoreParserMock{} var rfs ReverseFS - // If the root directory is excluded, all files and subdirs should be excluded - // as well - rfs.cExclude = []string{""} - verifyExcluded(t, &rfs, []string{"", "foo", "foo/bar"}) + rfs.excluder = ignorerMock + + if rfs.isExcluded(configfile.ConfDefaultName) { + t.Error("Should not exclude translated config") + } + if ignorerMock.calledWith != "" { + t.Error("Should not call IgnoreParser for translated config") + } +} + +func TestShouldNoCallIgnoreParserForDirIV(t *testing.T) { + ignorerMock := &IgnoreParserMock{} + var rfs ReverseFS + rfs.excluder = ignorerMock + + if rfs.isExcluded(nametransform.DirIVFilename) { + t.Error("Should not exclude DirIV") + } + if ignorerMock.calledWith != "" { + t.Error("Should not call IgnoreParser for DirIV") + } } diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index b0c34ef5..a94059cd 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "syscall" "golang.org/x/sys/unix" @@ -16,13 +15,14 @@ import ( "github.com/rfjakob/gocryptfs/internal/configfile" "github.com/rfjakob/gocryptfs/internal/contentenc" "github.com/rfjakob/gocryptfs/internal/cryptocore" - "github.com/rfjakob/gocryptfs/internal/ctlsock" "github.com/rfjakob/gocryptfs/internal/exitcodes" "github.com/rfjakob/gocryptfs/internal/fusefrontend" "github.com/rfjakob/gocryptfs/internal/nametransform" "github.com/rfjakob/gocryptfs/internal/pathiv" "github.com/rfjakob/gocryptfs/internal/syscallcompat" "github.com/rfjakob/gocryptfs/internal/tlog" + + "github.com/sabhiram/go-gitignore" ) // ReverseFS implements the pathfs.FileSystem interface and provides an @@ -38,9 +38,8 @@ type ReverseFS struct { nameTransform *nametransform.NameTransform // Content encryption helper contentEnc *contentenc.ContentEnc - // Relative ciphertext paths to exclude (hide) from the user. Used by -exclude. - // With -plaintextnames, these are relative *plaintext* paths. - cExclude []string + // Tests wheter a path is excluded (hiden) from the user. Used by -exclude. + excluder ignore.IgnoreParser } var _ pathfs.FileSystem = &ReverseFS{} @@ -59,18 +58,12 @@ func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.Na contentEnc: c, } if len(args.Exclude) > 0 { - for _, dirty := range args.Exclude { - clean := ctlsock.SanitizePath(dirty) - if clean != dirty { - tlog.Warn.Printf("-exclude: non-canonical path %q has been interpreted as %q", dirty, clean) - } - if clean == "" { - tlog.Fatal.Printf("-exclude: excluding the root dir %q makes no sense", clean) - os.Exit(exitcodes.ExcludeError) - } - fs.cExclude = append(fs.cExclude, clean) + excluder, err := ignore.CompileIgnoreLines(args.Exclude...) + if err != nil { + tlog.Fatal.Printf("Error compiling exclusion rules: %q", err) + os.Exit(exitcodes.ExcludeError) } - tlog.Debug.Printf("-exclude: %v -> %v", fs.args.Exclude, fs.cExclude) + fs.excluder = excluder } return fs } @@ -89,6 +82,10 @@ func relDir(path string) string { // isExcluded finds out if relative ciphertext path "relPath" is excluded // (used when -exclude is passed by the user) func (rfs *ReverseFS) isExcluded(relPath string) bool { + if rfs.excluder == nil { + return false + } + if rfs.isTranslatedConfig(relPath) || rfs.isDirIV(relPath) { return false } @@ -102,21 +99,7 @@ func (rfs *ReverseFS) isExcluded(relPath string) bool { } return false } - for _, e := range rfs.cExclude { - // If the root dir is excluded, everything is excluded. - if e == "" { - return true - } - // This exact path is excluded - if e == decPath { - return true - } - // Files inside an excluded directory are also excluded - if strings.HasPrefix(decPath, e+"/") { - return true - } - } - return false + return rfs.excluder.MatchesPath(decPath) } // isDirIV determines if the path points to a gocryptfs.diriv file @@ -378,7 +361,7 @@ func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse. // cDir is the relative ciphertext path to the directory these entries are // from. func (rfs *ReverseFS) excludeDirEntries(cDir string, entries []fuse.DirEntry) (filtered []fuse.DirEntry) { - if rfs.cExclude == nil { + if rfs.excluder == nil { return entries } filtered = make([]fuse.DirEntry, 0, len(entries)) diff --git a/tests/reverse/exclude_test.go b/tests/reverse/exclude_test.go index 150b3584..62b03795 100644 --- a/tests/reverse/exclude_test.go +++ b/tests/reverse/exclude_test.go @@ -15,11 +15,19 @@ const xxx = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx /* tree exclude_test_fs exclude_test_fs/ +├── bkp1~ ├── dir1 │ ├── file1 │ ├── file2 +│ ├── exclude +│ ├── longbkp1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx~ │ ├── longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -│ └── longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +│ ├── longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +│ ├── longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +│ └── subdir1 +│ ├── exclude +│ └── subdir2 +│ └── exclude ├── dir2 │ ├── file │ ├── longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx @@ -30,11 +38,13 @@ exclude_test_fs/ ├── file1 ├── file2 ├── longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -│ └── file +│ └── file1 ├── longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +│ ├── bkp~ │ └── file ├── longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -└── longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +├── longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +└── longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx */ func ctlsockEncryptPath(t *testing.T, sock string, path string) string { @@ -47,26 +57,46 @@ func ctlsockEncryptPath(t *testing.T, sock string, path string) string { } func testExclude(t *testing.T, flag string) { + pPatterns := []string{ + "file1", // matches file1 anywhere + "!longdir1" + xxx + "/file1", // ! includes an otherwise file + "file2/", // a trailing slash matches only a directory + "dir1/file2", // matches file2 inside dir1 anywhere + "#file2", // comments are ignored + "dir2", // excludes the whole directory + "longfile2" + xxx, // matches longfile2 anywhere + "/longfile3" + xxx, // a leading / anchors the match at the root + "*~", // wildcards are supported + "dir1/**/exclude", // ** matches any number of directories + } pOk := []string{ "file2", - "dir1/file1", "dir1/longfile1" + xxx, + "dir1/longfile3" + xxx, "longdir1" + xxx, - "longdir1" + xxx + "/file", + "longdir1" + xxx + "/file1", + "longdir2" + xxx + "/file", "longfile1" + xxx, } pExclude := []string{ - "file1", + "bkp1~", + "dir1/file1", "dir1/file2", + "dir1/exclude", + "dir1/longbkp1" + xxx + "~", "dir1/longfile2" + xxx, + "dir1/subdir1/exclude", + "dir1/subdir1/subdir2/exclude", "dir2", "dir2/file", - "dir2/file/xxx", - "dir2/subdir", - "dir2/subdir/file", "dir2/longdir1" + xxx + "/file", "dir2/longfile." + xxx, + "dir2/subdir", + "dir2/subdir/file", + "file1", + "longdir2" + xxx + "/bkp~", "longfile2" + xxx, + "longfile3" + xxx, } // Mount reverse fs mnt, err := ioutil.TempDir(test_helpers.TmpDir, "TestExclude") @@ -75,7 +105,7 @@ func testExclude(t *testing.T, flag string) { } sock := mnt + ".sock" cliArgs := []string{"-reverse", "-extpass", "echo test", "-ctlsock", sock} - for _, v := range pExclude { + for _, v := range pPatterns { cliArgs = append(cliArgs, flag, v) } if plaintextnames { diff --git a/tests/reverse/exclude_test_fs/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file b/tests/reverse/exclude_test_fs/bkp1~ similarity index 100% rename from tests/reverse/exclude_test_fs/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file rename to tests/reverse/exclude_test_fs/bkp1~ diff --git a/tests/reverse/exclude_test_fs/dir1/exclude b/tests/reverse/exclude_test_fs/dir1/exclude new file mode 100644 index 00000000..e69de29b diff --git a/tests/reverse/exclude_test_fs/dir1/longbkp1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx~ b/tests/reverse/exclude_test_fs/dir1/longbkp1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx~ new file mode 100644 index 00000000..e69de29b diff --git a/tests/reverse/exclude_test_fs/dir1/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx b/tests/reverse/exclude_test_fs/dir1/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx new file mode 100644 index 00000000..e69de29b diff --git a/tests/reverse/exclude_test_fs/dir1/subdir1/exclude b/tests/reverse/exclude_test_fs/dir1/subdir1/exclude new file mode 100644 index 00000000..e69de29b diff --git a/tests/reverse/exclude_test_fs/dir1/subdir1/subdir2/exclude b/tests/reverse/exclude_test_fs/dir1/subdir1/subdir2/exclude new file mode 100644 index 00000000..e69de29b diff --git a/tests/reverse/exclude_test_fs/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file1 b/tests/reverse/exclude_test_fs/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file1 new file mode 100644 index 00000000..e69de29b diff --git a/tests/reverse/exclude_test_fs/longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/bkp~ b/tests/reverse/exclude_test_fs/longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/bkp~ new file mode 100644 index 00000000..e69de29b diff --git a/tests/reverse/exclude_test_fs/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx b/tests/reverse/exclude_test_fs/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx new file mode 100644 index 00000000..e69de29b From 8a22bef433392b7af04141485e4ddc60221dbb72 Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Sun, 17 Feb 2019 20:54:08 -0300 Subject: [PATCH 04/18] Removed duplicated calls to decryptPath. isExcluded needs to decryptPath, so it was made to return the results of this function to that the Fuse callbacks don't need to call decryptPath again. --- .../fusefrontend_reverse/isexcluded_test.go | 34 ++++++--- internal/fusefrontend_reverse/rfile.go | 11 ++- internal/fusefrontend_reverse/rfs.go | 72 +++++++++++-------- internal/fusefrontend_reverse/rpath.go | 16 ++--- 4 files changed, 77 insertions(+), 56 deletions(-) diff --git a/internal/fusefrontend_reverse/isexcluded_test.go b/internal/fusefrontend_reverse/isexcluded_test.go index 8c2dc2ea..7e04349b 100644 --- a/internal/fusefrontend_reverse/isexcluded_test.go +++ b/internal/fusefrontend_reverse/isexcluded_test.go @@ -18,19 +18,12 @@ func (parser *IgnoreParserMock) MatchesPath(f string) bool { // Note: See also the integration tests in // tests/reverse/exclude_test.go -func TestShouldReturnFalseIfThereAreNoExclusions(t *testing.T) { - var rfs ReverseFS - if rfs.isExcluded("any/path") { - t.Error("Should not exclude any path is no exclusions were specified") - } -} - -func TestShouldNoCallIgnoreParserForTranslatedConfig(t *testing.T) { +func TestShouldNotCallIgnoreParserForTranslatedConfig(t *testing.T) { ignorerMock := &IgnoreParserMock{} var rfs ReverseFS rfs.excluder = ignorerMock - if rfs.isExcluded(configfile.ConfDefaultName) { + if excluded, _, _ := rfs.isExcludedCipher(configfile.ConfDefaultName); excluded { t.Error("Should not exclude translated config") } if ignorerMock.calledWith != "" { @@ -38,15 +31,34 @@ func TestShouldNoCallIgnoreParserForTranslatedConfig(t *testing.T) { } } -func TestShouldNoCallIgnoreParserForDirIV(t *testing.T) { +func TestShouldNotCallIgnoreParserForDirIV(t *testing.T) { ignorerMock := &IgnoreParserMock{} var rfs ReverseFS rfs.excluder = ignorerMock - if rfs.isExcluded(nametransform.DirIVFilename) { + if excluded, _, _ := rfs.isExcludedCipher(nametransform.DirIVFilename); excluded { t.Error("Should not exclude DirIV") } if ignorerMock.calledWith != "" { t.Error("Should not call IgnoreParser for DirIV") } } + +func TestShouldReturnFalseIfThereAreNoExclusions(t *testing.T) { + var rfs ReverseFS + if rfs.isExcludedPlain("any/path") { + t.Error("Should not exclude any path if no exclusions were specified") + } +} + +func TestShouldCallIgnoreParserToCheckExclusion(t *testing.T) { + ignorerMock := &IgnoreParserMock{} + var rfs ReverseFS + rfs.excluder = ignorerMock + + rfs.isExcludedPlain("some/path") + if ignorerMock.calledWith != "some/path" { + t.Error("Failed to call IgnoreParser") + } + +} diff --git a/internal/fusefrontend_reverse/rfile.go b/internal/fusefrontend_reverse/rfile.go index 75932cb1..9c31681d 100644 --- a/internal/fusefrontend_reverse/rfile.go +++ b/internal/fusefrontend_reverse/rfile.go @@ -34,19 +34,16 @@ type reverseFile struct { var inodeTable syncmap.Map -// newFile decrypts and opens the path "relPath" and returns a reverseFile +// newFile receives a ciphered path "relPath" and its corresponding +// decrypted path "pRelPath", opens it and returns a reverseFile // object. The backing file descriptor is always read-only. -func (rfs *ReverseFS) newFile(relPath string) (*reverseFile, fuse.Status) { - if rfs.isExcluded(relPath) { +func (rfs *ReverseFS) newFile(relPath string, pRelPath string) (*reverseFile, fuse.Status) { + if rfs.isExcludedPlain(pRelPath) { // Excluded paths should have been filtered out beforehand. Better safe // than sorry. tlog.Warn.Printf("BUG: newFile: received excluded path %q. This should not happen.", relPath) return nil, fuse.ENOENT } - pRelPath, err := rfs.decryptPath(relPath) - if err != nil { - return nil, fuse.ToStatus(err) - } dir := filepath.Dir(pRelPath) dirfd, err := syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, dir) if err != nil { diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index a94059cd..56558ed8 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -79,27 +79,26 @@ func relDir(path string) string { return dir } -// isExcluded finds out if relative ciphertext path "relPath" is excluded -// (used when -exclude is passed by the user) -func (rfs *ReverseFS) isExcluded(relPath string) bool { - if rfs.excluder == nil { - return false - } - +// isExcludedCipher finds out if relative ciphertext path "relPath" is +// excluded (used when -exclude is passed by the user). +// If relPath is not a special file, it returns the decrypted path or error +// from decryptPath for convenience. +func (rfs *ReverseFS) isExcludedCipher(relPath string) (bool, string, error) { if rfs.isTranslatedConfig(relPath) || rfs.isDirIV(relPath) { - return false + return false, "", nil } if rfs.isNameFile(relPath) { relPath = nametransform.RemoveLongNameSuffix(relPath) } decPath, err := rfs.decryptPath(relPath) - if err != nil { - if err != syscall.ENOENT { - tlog.Warn.Printf("Error decrypting path %q", relPath) - } - return false - } - return rfs.excluder.MatchesPath(decPath) + excluded := err == nil && rfs.isExcludedPlain(decPath) + return excluded, decPath, err +} + +// isExcludedPlain finds out if the plaintext path "pPath" is +// excluded (used when -exclude is passed by the user). +func (rfs *ReverseFS) isExcludedPlain(pPath string) bool { + return rfs.excluder != nil && rfs.excluder.MatchesPath(pPath) } // isDirIV determines if the path points to a gocryptfs.diriv file @@ -137,14 +136,18 @@ func (rfs *ReverseFS) isTranslatedConfig(relPath string) bool { // GetAttr - FUSE call // "relPath" is the relative ciphertext path func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) { - if rfs.isExcluded(relPath) { + excluded, pPath, err := rfs.isExcludedCipher(relPath) + if excluded { return nil, fuse.ENOENT } + if err != nil { + return nil, fuse.ToStatus(err) + } // Handle "gocryptfs.conf" if rfs.isTranslatedConfig(relPath) { absConfPath, _ := rfs.abs(configfile.ConfReverseName, nil) var st syscall.Stat_t - err := syscall.Lstat(absConfPath, &st) + err = syscall.Lstat(absConfPath, &st) if err != nil { return nil, fuse.ToStatus(err) } @@ -179,7 +182,7 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr } return &a, status } - dirfd, name, err := rfs.openBackingDir(relPath) + dirfd, name, err := rfs.openBackingDir(pPath) if err != nil { return nil, fuse.ToStatus(err) } @@ -221,9 +224,13 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr // Access - FUSE call func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) fuse.Status { - if rfs.isExcluded(relPath) { + excluded, pPath, err := rfs.isExcludedCipher(relPath) + if excluded { return fuse.ENOENT } + if err != nil { + return fuse.ToStatus(err) + } if rfs.isTranslatedConfig(relPath) || rfs.isDirIV(relPath) || rfs.isNameFile(relPath) { // access(2) R_OK flag for checking if the file is readable, always 4 as defined in POSIX. ROK := uint32(0x4) @@ -233,7 +240,7 @@ func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) } return fuse.EPERM } - dirfd, name, err := rfs.openBackingDir(relPath) + dirfd, name, err := rfs.openBackingDir(pPath) if err != nil { return fuse.ToStatus(err) } @@ -244,9 +251,13 @@ func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) // Open - FUSE call func (rfs *ReverseFS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { - if rfs.isExcluded(relPath) { + excluded, pPath, err := rfs.isExcludedCipher(relPath) + if excluded { return nil, fuse.ENOENT } + if err != nil { + return nil, fuse.ToStatus(err) + } if rfs.isTranslatedConfig(relPath) { return rfs.loopbackfs.Open(configfile.ConfReverseName, flags, context) } @@ -256,7 +267,7 @@ func (rfs *ReverseFS) Open(relPath string, flags uint32, context *fuse.Context) if rfs.isNameFile(relPath) { return rfs.newNameFile(relPath) } - return rfs.newFile(relPath) + return rfs.newFile(relPath, pPath) } func (rfs *ReverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEntry) ([]fuse.DirEntry, fuse.Status) { @@ -286,10 +297,10 @@ func (rfs *ReverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEn // OpenDir - FUSE readdir call func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { - if rfs.isExcluded(cipherPath) { + excluded, relPath, err := rfs.isExcludedCipher(cipherPath) + if excluded { return nil, fuse.ENOENT } - relPath, err := rfs.decryptPath(cipherPath) if err != nil { return nil, fuse.ToStatus(err) } @@ -369,7 +380,7 @@ func (rfs *ReverseFS) excludeDirEntries(cDir string, entries []fuse.DirEntry) (f // filepath.Join handles the case of cipherPath="" correctly: // Join("", "foo") -> "foo". This does not: cipherPath + "/" + name" p := filepath.Join(cDir, entry.Name) - if rfs.isExcluded(p) { + if excluded, _, _ := rfs.isExcludedCipher(p); excluded { // Skip file continue } @@ -384,7 +395,8 @@ func (rfs *ReverseFS) excludeDirEntries(cDir string, entries []fuse.DirEntry) (f // it's worth, so we just ignore the path and always return info about the // backing storage root dir. func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut { - if rfs.isExcluded(relPath) { + excluded, _, _ := rfs.isExcludedCipher(relPath) + if excluded { return nil } var s syscall.Statfs_t @@ -399,10 +411,14 @@ func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut { // Readlink - FUSE call func (rfs *ReverseFS) Readlink(relPath string, context *fuse.Context) (string, fuse.Status) { - if rfs.isExcluded(relPath) { + excluded, pPath, err := rfs.isExcludedCipher(relPath) + if excluded { return "", fuse.ENOENT } - dirfd, name, err := rfs.openBackingDir(relPath) + if err != nil { + return "", fuse.ToStatus(err) + } + dirfd, name, err := rfs.openBackingDir(pPath) if err != nil { return "", fuse.ToStatus(err) } diff --git a/internal/fusefrontend_reverse/rpath.go b/internal/fusefrontend_reverse/rpath.go index 7115426f..2da83793 100644 --- a/internal/fusefrontend_reverse/rpath.go +++ b/internal/fusefrontend_reverse/rpath.go @@ -96,16 +96,12 @@ func (rfs *ReverseFS) decryptPath(relPath string) (string, error) { return pRelPath, nil } -// openBackingDir decrypt the relative ciphertext path "cRelPath", opens -// the directory that contains the target file/dir and returns the fd to -// the directory and the decrypted name of the target file. -// The fd/name pair is intended for use with fchownat and friends. -func (rfs *ReverseFS) openBackingDir(cRelPath string) (dirfd int, pName string, err error) { - // Decrypt relative path - pRelPath, err := rfs.decryptPath(cRelPath) - if err != nil { - return -1, "", err - } +// openBackingDir receives an already decrypted relative path +// "pRelPath", opens the directory that contains the target file/dir +// and returns the fd to the directory and the decrypted name of the +// target file. The fd/name pair is intended for use with fchownat and +// friends. +func (rfs *ReverseFS) openBackingDir(pRelPath string) (dirfd int, pName string, err error) { // Open directory, safe against symlink races pDir := filepath.Dir(pRelPath) dirfd, err = syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, pDir) From ea82f062068cf3b3e93fa0b386f12782149df7ed Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Thu, 21 Feb 2019 20:27:17 -0300 Subject: [PATCH 05/18] Move functions related to file exclusion (reverse mode) to new file. --- internal/fusefrontend_reverse/excluder.go | 47 +++++++++++++++++++ .../{isexcluded_test.go => excluder_test.go} | 10 ++++ internal/fusefrontend_reverse/rfs.go | 33 +------------ 3 files changed, 58 insertions(+), 32 deletions(-) create mode 100644 internal/fusefrontend_reverse/excluder.go rename internal/fusefrontend_reverse/{isexcluded_test.go => excluder_test.go} (85%) diff --git a/internal/fusefrontend_reverse/excluder.go b/internal/fusefrontend_reverse/excluder.go new file mode 100644 index 00000000..fa7f9089 --- /dev/null +++ b/internal/fusefrontend_reverse/excluder.go @@ -0,0 +1,47 @@ +package fusefrontend_reverse + +import ( + "os" + + "github.com/rfjakob/gocryptfs/internal/exitcodes" + "github.com/rfjakob/gocryptfs/internal/fusefrontend" + "github.com/rfjakob/gocryptfs/internal/nametransform" + "github.com/rfjakob/gocryptfs/internal/tlog" + + "github.com/sabhiram/go-gitignore" +) + +// prepareExcluder creates an object to check if paths are excluded +// based on the patterns specified in the command line. +func (rfs *ReverseFS) prepareExcluder(args fusefrontend.Args) { + if len(args.Exclude) > 0 { + excluder, err := ignore.CompileIgnoreLines(args.Exclude...) + if err != nil { + tlog.Fatal.Printf("Error compiling exclusion rules: %q", err) + os.Exit(exitcodes.ExcludeError) + } + rfs.excluder = excluder + } +} + +// isExcludedCipher finds out if relative ciphertext path "relPath" is +// excluded (used when -exclude is passed by the user). +// If relPath is not a special file, it returns the decrypted path or error +// from decryptPath for convenience. +func (rfs *ReverseFS) isExcludedCipher(relPath string) (bool, string, error) { + if rfs.isTranslatedConfig(relPath) || rfs.isDirIV(relPath) { + return false, "", nil + } + if rfs.isNameFile(relPath) { + relPath = nametransform.RemoveLongNameSuffix(relPath) + } + decPath, err := rfs.decryptPath(relPath) + excluded := err == nil && rfs.isExcludedPlain(decPath) + return excluded, decPath, err +} + +// isExcludedPlain finds out if the plaintext path "pPath" is +// excluded (used when -exclude is passed by the user). +func (rfs *ReverseFS) isExcludedPlain(pPath string) bool { + return rfs.excluder != nil && rfs.excluder.MatchesPath(pPath) +} diff --git a/internal/fusefrontend_reverse/isexcluded_test.go b/internal/fusefrontend_reverse/excluder_test.go similarity index 85% rename from internal/fusefrontend_reverse/isexcluded_test.go rename to internal/fusefrontend_reverse/excluder_test.go index 7e04349b..ca6bc4d4 100644 --- a/internal/fusefrontend_reverse/isexcluded_test.go +++ b/internal/fusefrontend_reverse/excluder_test.go @@ -4,9 +4,19 @@ import ( "testing" "github.com/rfjakob/gocryptfs/internal/configfile" + "github.com/rfjakob/gocryptfs/internal/fusefrontend" "github.com/rfjakob/gocryptfs/internal/nametransform" ) +func TestShouldNoCreateExcluderIfNoPattersWereSpecified(t *testing.T) { + var rfs ReverseFS + var args fusefrontend.Args + rfs.prepareExcluder(args) + if rfs.excluder != nil { + t.Error("Should not have created excluder") + } +} + type IgnoreParserMock struct { calledWith string } diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 56558ed8..3841d11f 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -2,7 +2,6 @@ package fusefrontend_reverse import ( "fmt" - "os" "path/filepath" "syscall" @@ -15,7 +14,6 @@ import ( "github.com/rfjakob/gocryptfs/internal/configfile" "github.com/rfjakob/gocryptfs/internal/contentenc" "github.com/rfjakob/gocryptfs/internal/cryptocore" - "github.com/rfjakob/gocryptfs/internal/exitcodes" "github.com/rfjakob/gocryptfs/internal/fusefrontend" "github.com/rfjakob/gocryptfs/internal/nametransform" "github.com/rfjakob/gocryptfs/internal/pathiv" @@ -57,14 +55,7 @@ func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.Na nameTransform: n, contentEnc: c, } - if len(args.Exclude) > 0 { - excluder, err := ignore.CompileIgnoreLines(args.Exclude...) - if err != nil { - tlog.Fatal.Printf("Error compiling exclusion rules: %q", err) - os.Exit(exitcodes.ExcludeError) - } - fs.excluder = excluder - } + fs.prepareExcluder(args) return fs } @@ -79,28 +70,6 @@ func relDir(path string) string { return dir } -// isExcludedCipher finds out if relative ciphertext path "relPath" is -// excluded (used when -exclude is passed by the user). -// If relPath is not a special file, it returns the decrypted path or error -// from decryptPath for convenience. -func (rfs *ReverseFS) isExcludedCipher(relPath string) (bool, string, error) { - if rfs.isTranslatedConfig(relPath) || rfs.isDirIV(relPath) { - return false, "", nil - } - if rfs.isNameFile(relPath) { - relPath = nametransform.RemoveLongNameSuffix(relPath) - } - decPath, err := rfs.decryptPath(relPath) - excluded := err == nil && rfs.isExcludedPlain(decPath) - return excluded, decPath, err -} - -// isExcludedPlain finds out if the plaintext path "pPath" is -// excluded (used when -exclude is passed by the user). -func (rfs *ReverseFS) isExcludedPlain(pPath string) bool { - return rfs.excluder != nil && rfs.excluder.MatchesPath(pPath) -} - // isDirIV determines if the path points to a gocryptfs.diriv file func (rfs *ReverseFS) isDirIV(relPath string) bool { if rfs.args.PlaintextNames { From 150cf2ed8cd8c8d2da5a691b2ea6e727dbe71a54 Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Fri, 22 Feb 2019 19:51:41 -0300 Subject: [PATCH 06/18] Adds -exclude-wildcard command-line option. -exclude keeps the original behaviour by matching from the root of the mounted filesystem. The new option -exclude-wildcard (or -ew) matches anywhere (unless tha pattern begins with '/'). --- cli_args.go | 8 +++++--- internal/fusefrontend/args.go | 6 +++++- internal/fusefrontend_reverse/excluder.go | 20 +++++++++++++++++-- .../fusefrontend_reverse/excluder_test.go | 14 +++++++++++++ mount.go | 19 +++++++++--------- tests/reverse/exclude_test.go | 4 ++-- 6 files changed, 54 insertions(+), 17 deletions(-) diff --git a/cli_args.go b/cli_args.go index a4da85c7..0d8f222b 100644 --- a/cli_args.go +++ b/cli_args.go @@ -34,8 +34,8 @@ type argContainer struct { dev, nodev, suid, nosuid, exec, noexec, rw, ro bool masterkey, mountpoint, cipherdir, cpuprofile, extpass, memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string - // For reverse mode, --exclude is available. It can be specified multiple times. - exclude multipleStrings + // For reverse mode, several ways to specify exclusions. All can be specified multiple times. + exclude, excludeWildcard multipleStrings // Configuration file name override config string notifypid, scryptn int @@ -187,9 +187,11 @@ func parseCliOpts() (args argContainer) { flagSet.StringVar(&args.force_owner, "force_owner", "", "uid:gid pair to coerce ownership") flagSet.StringVar(&args.trace, "trace", "", "Write execution trace to file") - // -e, --exclude + // Exclusion options flagSet.Var(&args.exclude, "e", "Alias for -exclude") flagSet.Var(&args.exclude, "exclude", "Exclude relative path from reverse view") + flagSet.Var(&args.excludeWildcard, "ew", "Alias for -exclude-wildcard") + flagSet.Var(&args.excludeWildcard, "exclude-wildcard", "Exclude path from reverse view, supporting wildcards") flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+ "successful mount - used internally for daemonization") diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go index 5fb72cd0..715a011c 100644 --- a/internal/fusefrontend/args.go +++ b/internal/fusefrontend/args.go @@ -30,6 +30,10 @@ type Args struct { SerializeReads bool // Force decode even if integrity check fails (openSSL only) ForceDecode bool - // Exclude is a list of paths to make inaccessible + // Exclude is a list of paths to make inaccessible, starting match at + // the filesystem root Exclude []string + // ExcludeWildcards is a list of paths to make inaccessible, matched + // anywhere, and supporting wildcards + ExcludeWildcard []string } diff --git a/internal/fusefrontend_reverse/excluder.go b/internal/fusefrontend_reverse/excluder.go index fa7f9089..7eb9e62d 100644 --- a/internal/fusefrontend_reverse/excluder.go +++ b/internal/fusefrontend_reverse/excluder.go @@ -14,8 +14,8 @@ import ( // prepareExcluder creates an object to check if paths are excluded // based on the patterns specified in the command line. func (rfs *ReverseFS) prepareExcluder(args fusefrontend.Args) { - if len(args.Exclude) > 0 { - excluder, err := ignore.CompileIgnoreLines(args.Exclude...) + if len(args.Exclude) > 0 || len(args.ExcludeWildcard) > 0 { + excluder, err := ignore.CompileIgnoreLines(getExclusionPatterns(args)...) if err != nil { tlog.Fatal.Printf("Error compiling exclusion rules: %q", err) os.Exit(exitcodes.ExcludeError) @@ -24,6 +24,22 @@ func (rfs *ReverseFS) prepareExcluder(args fusefrontend.Args) { } } +// getExclusionPatters prepares a list of patterns to be excluded. +// Patterns passed in the -exclude command line option are prefixed +// with a leading '/' to preserve backwards compatibility (before +// wildcard matching was implemented, exclusions always were matched +// agains the full path). +func getExclusionPatterns(args fusefrontend.Args) []string { + patterns := make([]string, len(args.Exclude)+len(args.ExcludeWildcard)) + // add -exclude + for i, p := range args.Exclude { + patterns[i] = "/" + p + } + // add -exclude-wildcard + copy(patterns[len(args.Exclude):], args.ExcludeWildcard) + return patterns +} + // isExcludedCipher finds out if relative ciphertext path "relPath" is // excluded (used when -exclude is passed by the user). // If relPath is not a special file, it returns the decrypted path or error diff --git a/internal/fusefrontend_reverse/excluder_test.go b/internal/fusefrontend_reverse/excluder_test.go index ca6bc4d4..59ad19e3 100644 --- a/internal/fusefrontend_reverse/excluder_test.go +++ b/internal/fusefrontend_reverse/excluder_test.go @@ -1,6 +1,7 @@ package fusefrontend_reverse import ( + "reflect" "testing" "github.com/rfjakob/gocryptfs/internal/configfile" @@ -17,6 +18,19 @@ func TestShouldNoCreateExcluderIfNoPattersWereSpecified(t *testing.T) { } } +func TestShouldPrefixExcludeValuesWithSlash(t *testing.T) { + var args fusefrontend.Args + args.Exclude = []string{"file1", "dir1/file2.txt"} + args.ExcludeWildcard = []string{"*~", "build/*.o"} + + expected := []string{"/file1", "/dir1/file2.txt", "*~", "build/*.o"} + + patterns := getExclusionPatterns(args) + if !reflect.DeepEqual(patterns, expected) { + t.Errorf("expected %q, got %q", expected, patterns) + } +} + type IgnoreParserMock struct { calledWith string } diff --git a/mount.go b/mount.go index 03cbc0bd..fd5dbc20 100644 --- a/mount.go +++ b/mount.go @@ -231,15 +231,16 @@ func initFuseFrontend(args *argContainer) (pfs pathfs.FileSystem, wipeKeys func( args.allow_other = true } frontendArgs := fusefrontend.Args{ - Cipherdir: args.cipherdir, - PlaintextNames: args.plaintextnames, - LongNames: args.longnames, - ConfigCustom: args._configCustom, - NoPrealloc: args.noprealloc, - SerializeReads: args.serialize_reads, - ForceDecode: args.forcedecode, - ForceOwner: args._forceOwner, - Exclude: args.exclude, + Cipherdir: args.cipherdir, + PlaintextNames: args.plaintextnames, + LongNames: args.longnames, + ConfigCustom: args._configCustom, + NoPrealloc: args.noprealloc, + SerializeReads: args.serialize_reads, + ForceDecode: args.forcedecode, + ForceOwner: args._forceOwner, + Exclude: args.exclude, + ExcludeWildcard: args.excludeWildcard, } // confFile is nil when "-zerokey" or "-masterkey" was used if confFile != nil { diff --git a/tests/reverse/exclude_test.go b/tests/reverse/exclude_test.go index 62b03795..1662f935 100644 --- a/tests/reverse/exclude_test.go +++ b/tests/reverse/exclude_test.go @@ -150,6 +150,6 @@ func encryptExcludeTestPaths(t *testing.T, socket string, pRelPaths []string) (o } func TestExclude(t *testing.T) { - testExclude(t, "-exclude") - testExclude(t, "-e") + testExclude(t, "-exclude-wildcard") + testExclude(t, "-ew") } From 2f4527f089b4aa2fff365cc8ca86834aa94a4af0 Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Sat, 23 Feb 2019 18:45:48 -0300 Subject: [PATCH 07/18] Adds -exclude-from command-line option. It allows the user to specify a file containg exclusion patters. The patterns are "wildcard" ones, that is, matching anywhere. --- cli_args.go | 3 +- internal/fusefrontend/args.go | 3 ++ internal/fusefrontend_reverse/excluder.go | 22 ++++++++++- .../fusefrontend_reverse/excluder_test.go | 38 +++++++++++++++++++ mount.go | 1 + 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/cli_args.go b/cli_args.go index 0d8f222b..9e9a2f0c 100644 --- a/cli_args.go +++ b/cli_args.go @@ -35,7 +35,7 @@ type argContainer struct { masterkey, mountpoint, cipherdir, cpuprofile, extpass, memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string // For reverse mode, several ways to specify exclusions. All can be specified multiple times. - exclude, excludeWildcard multipleStrings + exclude, excludeWildcard, excludeFrom multipleStrings // Configuration file name override config string notifypid, scryptn int @@ -192,6 +192,7 @@ func parseCliOpts() (args argContainer) { flagSet.Var(&args.exclude, "exclude", "Exclude relative path from reverse view") flagSet.Var(&args.excludeWildcard, "ew", "Alias for -exclude-wildcard") flagSet.Var(&args.excludeWildcard, "exclude-wildcard", "Exclude path from reverse view, supporting wildcards") + flagSet.Var(&args.excludeFrom, "exclude-from", "File from which to read exclusion patterns (with -exclude-wildcard syntax)") flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+ "successful mount - used internally for daemonization") diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go index 715a011c..e767f281 100644 --- a/internal/fusefrontend/args.go +++ b/internal/fusefrontend/args.go @@ -36,4 +36,7 @@ type Args struct { // ExcludeWildcards is a list of paths to make inaccessible, matched // anywhere, and supporting wildcards ExcludeWildcard []string + // ExcludeFrom is a list of files from which to read exclusion patterns + // (with wildcard syntax) + ExcludeFrom []string } diff --git a/internal/fusefrontend_reverse/excluder.go b/internal/fusefrontend_reverse/excluder.go index 7eb9e62d..d5d91424 100644 --- a/internal/fusefrontend_reverse/excluder.go +++ b/internal/fusefrontend_reverse/excluder.go @@ -1,7 +1,9 @@ package fusefrontend_reverse import ( + "io/ioutil" "os" + "strings" "github.com/rfjakob/gocryptfs/internal/exitcodes" "github.com/rfjakob/gocryptfs/internal/fusefrontend" @@ -14,7 +16,7 @@ import ( // prepareExcluder creates an object to check if paths are excluded // based on the patterns specified in the command line. func (rfs *ReverseFS) prepareExcluder(args fusefrontend.Args) { - if len(args.Exclude) > 0 || len(args.ExcludeWildcard) > 0 { + if len(args.Exclude) > 0 || len(args.ExcludeWildcard) > 0 || len(args.ExcludeFrom) > 0 { excluder, err := ignore.CompileIgnoreLines(getExclusionPatterns(args)...) if err != nil { tlog.Fatal.Printf("Error compiling exclusion rules: %q", err) @@ -37,9 +39,27 @@ func getExclusionPatterns(args fusefrontend.Args) []string { } // add -exclude-wildcard copy(patterns[len(args.Exclude):], args.ExcludeWildcard) + // add -exclude-from + for _, file := range args.ExcludeFrom { + lines, err := getLines(file) + if err != nil { + tlog.Fatal.Printf("Error reading exclusion patterns: %q", err) + os.Exit(exitcodes.ExcludeError) + } + patterns = append(patterns, lines...) + } return patterns } +// getLines reads a file and splits it into lines +func getLines(file string) ([]string, error) { + buffer, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + return strings.Split(string(buffer), "\n"), nil +} + // isExcludedCipher finds out if relative ciphertext path "relPath" is // excluded (used when -exclude is passed by the user). // If relPath is not a special file, it returns the decrypted path or error diff --git a/internal/fusefrontend_reverse/excluder_test.go b/internal/fusefrontend_reverse/excluder_test.go index 59ad19e3..5217b5c4 100644 --- a/internal/fusefrontend_reverse/excluder_test.go +++ b/internal/fusefrontend_reverse/excluder_test.go @@ -1,6 +1,8 @@ package fusefrontend_reverse import ( + "io/ioutil" + "os" "reflect" "testing" @@ -31,6 +33,42 @@ func TestShouldPrefixExcludeValuesWithSlash(t *testing.T) { } } +func TestShouldReadExcludePatternsFromFiles(t *testing.T) { + tmpfile1, err := ioutil.TempFile("", "excludetest") + if err != nil { + t.Fatal(err) + } + exclude1 := tmpfile1.Name() + defer os.Remove(exclude1) + defer tmpfile1.Close() + + tmpfile2, err := ioutil.TempFile("", "excludetest") + if err != nil { + t.Fatal(err) + } + exclude2 := tmpfile2.Name() + defer os.Remove(exclude2) + defer tmpfile2.Close() + + tmpfile1.WriteString("file1.1\n") + tmpfile1.WriteString("file1.2\n") + tmpfile2.WriteString("file2.1\n") + tmpfile2.WriteString("file2.2\n") + + var args fusefrontend.Args + args.ExcludeWildcard = []string{"cmdline1"} + args.ExcludeFrom = []string{exclude1, exclude2} + + // An empty string is returned for the last empty line + // It's ignored when the patterns are actually compiled + expected := []string{"cmdline1", "file1.1", "file1.2", "", "file2.1", "file2.2", ""} + + patterns := getExclusionPatterns(args) + if !reflect.DeepEqual(patterns, expected) { + t.Errorf("expected %q, got %q", expected, patterns) + } +} + type IgnoreParserMock struct { calledWith string } diff --git a/mount.go b/mount.go index fd5dbc20..6c942409 100644 --- a/mount.go +++ b/mount.go @@ -241,6 +241,7 @@ func initFuseFrontend(args *argContainer) (pfs pathfs.FileSystem, wipeKeys func( ForceOwner: args._forceOwner, Exclude: args.exclude, ExcludeWildcard: args.excludeWildcard, + ExcludeFrom: args.excludeFrom, } // confFile is nil when "-zerokey" or "-masterkey" was used if confFile != nil { From 41e2d337c1d5ca6a1c3d2d297872dceb67ff0877 Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Sun, 24 Feb 2019 10:02:06 -0300 Subject: [PATCH 08/18] Documents new exclusion options. --- Documentation/MANPAGE.md | 82 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md index 5ebb0b4a..299e0c53 100644 --- a/Documentation/MANPAGE.md +++ b/Documentation/MANPAGE.md @@ -78,10 +78,29 @@ harvest enough entropy. #### -e PATH, -exclude PATH Only for reverse mode: exclude relative plaintext path from the encrypted -view. Can be passed multiple times. Example: +view, matching only from root of mounted filesystem. Can be passed multiple +times. Example: gocryptfs -reverse -exclude Music -exclude Movies /home/user /mnt/user.encrypted +See also `-exclude-wildcard`, `-exclude-from` and the [EXCLUDING FILES](#excluding-files) section. + +#### -ew PATH, -exclude-wildcard PATH +Only for reverse mode: exclude paths from the encrypted view, matching anywhere. +Wildcards supported. Can be passed multiple times. Example: + + gocryptfs -reverse -exclude-wildcard '*~' /home/user /mnt/user.encrypted + +See also `-exclude`, `-exclude-from` and the [EXCLUDING FILES](#excluding-files) section. + +#### -exclude-from FILE +Only for reverse mode: reads exclusion patters (using `-exclude-wildcard` syntax) +from a file. Can be passed multiple times. Example: + + gocryptfs -reverse -exclude-from ~/crypt-exclusions /home/user /mnt/user.encrypted + +See also `-exclude`, `-exclude-wildcard` and the [EXCLUDING FILES](#excluding-files) section. + #### -exec, -noexec Enable (`-exec`) or disable (`-noexec`) executables in a gocryptfs mount (default: `-exec`). If both are specified, `-noexec` takes precedence. @@ -393,6 +412,67 @@ automated testing as it does not provide any security. Stop option parsing. Helpful when CIPHERDIR may start with a dash "-". +EXCLUDING FILES +=============== + +In reverse mode, it is possible to exclude files from the encrypted view, using +the `-exclude`, `-exclude-wildcard` and `-exclude-from` options. + +`-exclude` matches complete paths, so `-exclude file.txt` only excludes a file +named `file.txt` in the root of the mounted filesystem; files named `file.txt` +in subdirectories are still visible. (This option is kept for compatibility +with the behavior up to version 1.6.x) + +`-exclude-wildcard` matches files anywhere, so `-exclude-wildcard file.txt` +excludes files named `file.txt` in any directory. If you want to match complete +paths, you can prefix the filename with a `/`: `-exclude-wildcard /file.txt` +excludes only `file.txt` in the root of the mounted filesystem. + +If there are many exclusions, you can use `-exclude-from` to read exclusion +patterns from a file. The syntax is that of `-exclude-wildcard`, so use a +leading `/` to match complete paths. + +The rules for exclusion are that of [gitignore](https://git-scm.com/docs/gitignore#_pattern_format). +In short: + +1. A blank line matches no files, so it can serve as a separator + for readability. +2. A line starting with `#` serves as a comment. Put a backslash (`\`) + in front of the first hash for patterns that begin with a hash. +3. Trailing spaces are ignored unless they are quoted with backslash (`\`). +4. An optional prefix `!` negates the pattern; any matching file + excluded by a previous pattern will become included again. It is not + possible to re-include a file if a parent directory of that file is + excluded. Put a backslash (`\`) in front of the first `!` for + patterns that begin with a literal `!`, for example, `\!important!.txt`. +5. If the pattern ends with a slash, it is removed for the purpose of the + following description, but it would only find a match with a directory. + In other words, `foo/` will match a directory foo and paths underneath it, + but will not match a regular file or a symbolic link foo. +6. If the pattern does not contain a slash `/`, it is treated as a shell glob + pattern and checked for a match against the pathname relative to the + root of the mounted filesystem. +7. Otherwise, the pattern is treated as a shell glob suitable for + consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the + pattern will not match a `/` in the pathname. For example, + `Documentation/*.html` matches `Documentation/git.html` but not + `Documentation/ppc/ppc.html` or `tools/perf/Documentation/perf.html`. +8. A leading slash matches the beginning of the pathname. For example, + `/*.c` matches `cat-file.c` but not `mozilla-sha1/sha1.c`. +9. Two consecutive asterisks (`**`) in patterns matched against full + pathname may have special meaning: + i. A leading `**` followed by a slash means match in all directories. + For example, `**/foo` matches file or directory `foo` anywhere, + the same as pattern `foo`. `**/foo/bar` matches file or directory + `bar` anywhere that is directly under directory `foo`. + ii. A trailing `/**` matches everything inside. For example, `abc/**` + matches all files inside directory `abc`, with infinite depth. + iii. A slash followed by two consecutive asterisks then a slash matches + zero or more directories. For example, `a/**/b` matches `a/b`, + `a/x/b`, `a/x/y/b` and so on. + iv. Other consecutive asterisks are considered invalid. + + EXAMPLES ======== From b169a12fe78031b2ce2f3d0b15c9ed2818007271 Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Sun, 24 Feb 2019 14:36:55 -0300 Subject: [PATCH 09/18] Optimizes OpenDir. Files are excluded before their names are encrypted, to avoid having the names decrypted again when checking for exclusions. --- internal/fusefrontend_reverse/rfs.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 3841d11f..bec0cd08 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -294,9 +294,11 @@ func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse. if !status.Ok() { return nil, status } - entries = rfs.excludeDirEntries(cipherPath, entries) + entries = rfs.excludeDirEntries(relPath, entries) return entries, fuse.OK } + // Filter out excluded entries + entries = rfs.excludeDirEntries(relPath, entries) // Allocate maximum possible number of virtual files. // If all files have long names we need a virtual ".name" file for each, // plus one for gocryptfs.diriv. @@ -332,24 +334,22 @@ func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse. } // Add virtual files entries = append(entries, virtualFiles[:nVirtual]...) - // Filter out excluded entries - entries = rfs.excludeDirEntries(cipherPath, entries) return entries, fuse.OK } // excludeDirEntries filters out directory entries that are "-exclude"d. -// cDir is the relative ciphertext path to the directory these entries are -// from. -func (rfs *ReverseFS) excludeDirEntries(cDir string, entries []fuse.DirEntry) (filtered []fuse.DirEntry) { +// pDir is the relative plaintext path to the directory these entries are +// from. The entries should be plaintext files. +func (rfs *ReverseFS) excludeDirEntries(pDir string, entries []fuse.DirEntry) (filtered []fuse.DirEntry) { if rfs.excluder == nil { return entries } filtered = make([]fuse.DirEntry, 0, len(entries)) for _, entry := range entries { - // filepath.Join handles the case of cipherPath="" correctly: - // Join("", "foo") -> "foo". This does not: cipherPath + "/" + name" - p := filepath.Join(cDir, entry.Name) - if excluded, _, _ := rfs.isExcludedCipher(p); excluded { + // filepath.Join handles the case of pDir="" correctly: + // Join("", "foo") -> "foo". This does not: pDir + "/" + name" + p := filepath.Join(pDir, entry.Name) + if rfs.isExcludedPlain(p) { // Skip file continue } From 56121812853cdf3c85443b77bdf7fdd48546f203 Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Wed, 6 Mar 2019 12:13:28 -0300 Subject: [PATCH 10/18] Make NameTransform an interface. This was done to allow it to be replace with a mock object in tests (specifically in excluder_test.go). --- internal/fusefrontend/fs.go | 8 ++-- internal/fusefrontend/xattr.go | 2 +- .../fusefrontend_reverse/excluder_test.go | 43 ++++++++++++++----- internal/fusefrontend_reverse/rfs.go | 6 +-- internal/nametransform/names.go | 21 +++++++++ 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index ff628e54..2c409427 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -35,7 +35,7 @@ type FS struct { // states dirIVLock sync.RWMutex // Filename encryption helper - nameTransform *nametransform.NameTransform + nameTransform nametransform.NameTransformer // Content encryption helper contentEnc *contentenc.ContentEnc // This lock is used by openWriteOnlyFile() to block concurrent opens while @@ -62,7 +62,7 @@ type FS struct { //var _ pathfs.FileSystem = &FS{} // Verify that interface is implemented. // NewFS returns a new encrypted FUSE overlay filesystem. -func NewFS(args Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *FS { +func NewFS(args Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *FS { if args.SerializeReads { serialize_reads.InitSerializer() } @@ -399,7 +399,7 @@ func (fs *FS) decryptSymlinkTarget(cData64 string) (string, error) { if cData64 == "" { return "", nil } - cData, err := fs.nameTransform.B64.DecodeString(cData64) + cData, err := fs.nameTransform.B64DecodeString(cData64) if err != nil { return "", err } @@ -472,7 +472,7 @@ func (fs *FS) encryptSymlinkTarget(data string) (cData64 string) { return "" } cData := fs.contentEnc.EncryptBlock([]byte(data), 0, nil) - cData64 = fs.nameTransform.B64.EncodeToString(cData) + cData64 = fs.nameTransform.B64EncodeToString(cData) return cData64 } diff --git a/internal/fusefrontend/xattr.go b/internal/fusefrontend/xattr.go index 1de9cac3..20e8db74 100644 --- a/internal/fusefrontend/xattr.go +++ b/internal/fusefrontend/xattr.go @@ -150,7 +150,7 @@ func (fs *FS) decryptXattrValue(cData []byte) (data []byte, err error) { } // This backward compatibility is needed to support old // file systems having xattr values base64-encoded. - cData, err2 := fs.nameTransform.B64.DecodeString(string(cData)) + cData, err2 := fs.nameTransform.B64DecodeString(string(cData)) if err2 != nil { // Looks like the value was not base64-encoded, but just corrupt. // Return the original decryption error: err1 diff --git a/internal/fusefrontend_reverse/excluder_test.go b/internal/fusefrontend_reverse/excluder_test.go index 5217b5c4..f73defc7 100644 --- a/internal/fusefrontend_reverse/excluder_test.go +++ b/internal/fusefrontend_reverse/excluder_test.go @@ -70,20 +70,27 @@ func TestShouldReadExcludePatternsFromFiles(t *testing.T) { } type IgnoreParserMock struct { + toExclude string calledWith string } func (parser *IgnoreParserMock) MatchesPath(f string) bool { parser.calledWith = f - return false + return f == parser.toExclude +} + +type NameTransformMock struct { + nametransform.NameTransform +} + +func (n *NameTransformMock) DecryptName(cipherName string, iv []byte) (string, error) { + return "mockdecrypt_" + cipherName, nil } // Note: See also the integration tests in // tests/reverse/exclude_test.go func TestShouldNotCallIgnoreParserForTranslatedConfig(t *testing.T) { - ignorerMock := &IgnoreParserMock{} - var rfs ReverseFS - rfs.excluder = ignorerMock + rfs, ignorerMock := createRFSWithMocks() if excluded, _, _ := rfs.isExcludedCipher(configfile.ConfDefaultName); excluded { t.Error("Should not exclude translated config") @@ -94,9 +101,7 @@ func TestShouldNotCallIgnoreParserForTranslatedConfig(t *testing.T) { } func TestShouldNotCallIgnoreParserForDirIV(t *testing.T) { - ignorerMock := &IgnoreParserMock{} - var rfs ReverseFS - rfs.excluder = ignorerMock + rfs, ignorerMock := createRFSWithMocks() if excluded, _, _ := rfs.isExcludedCipher(nametransform.DirIVFilename); excluded { t.Error("Should not exclude DirIV") @@ -106,6 +111,18 @@ func TestShouldNotCallIgnoreParserForDirIV(t *testing.T) { } } +func TestShouldDecryptPathAndReturnTrueForExcludedPath(t *testing.T) { + rfs, ignorerMock := createRFSWithMocks() + ignorerMock.toExclude = "mockdecrypt_file.txt" + + if excluded, _, _ := rfs.isExcludedCipher("file.txt"); !excluded { + t.Error("Should have excluded") + } + if ignorerMock.calledWith != "mockdecrypt_file.txt" { + t.Error("Didn't call IgnoreParser with decrypted path") + } +} + func TestShouldReturnFalseIfThereAreNoExclusions(t *testing.T) { var rfs ReverseFS if rfs.isExcludedPlain("any/path") { @@ -114,13 +131,19 @@ func TestShouldReturnFalseIfThereAreNoExclusions(t *testing.T) { } func TestShouldCallIgnoreParserToCheckExclusion(t *testing.T) { - ignorerMock := &IgnoreParserMock{} - var rfs ReverseFS - rfs.excluder = ignorerMock + rfs, ignorerMock := createRFSWithMocks() rfs.isExcludedPlain("some/path") if ignorerMock.calledWith != "some/path" { t.Error("Failed to call IgnoreParser") } +} +func createRFSWithMocks() (*ReverseFS, *IgnoreParserMock) { + ignorerMock := &IgnoreParserMock{} + nameTransformMock := &NameTransformMock{} + var rfs ReverseFS + rfs.excluder = ignorerMock + rfs.nameTransform = nameTransformMock + return &rfs, ignorerMock } diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index bec0cd08..340a162a 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -33,7 +33,7 @@ type ReverseFS struct { // Stores configuration arguments args fusefrontend.Args // Filename encryption helper - nameTransform *nametransform.NameTransform + nameTransform nametransform.NameTransformer // Content encryption helper contentEnc *contentenc.ContentEnc // Tests wheter a path is excluded (hiden) from the user. Used by -exclude. @@ -45,7 +45,7 @@ var _ pathfs.FileSystem = &ReverseFS{} // NewFS returns an encrypted FUSE overlay filesystem. // In this case (reverse mode) the backing directory is plain-text and // ReverseFS provides an encrypted view. -func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *ReverseFS { +func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *ReverseFS { initLongnameCache() fs := &ReverseFS{ // pathfs.defaultFileSystem returns ENOSYS for all operations @@ -403,7 +403,7 @@ func (rfs *ReverseFS) Readlink(relPath string, context *fuse.Context) (string, f nonce := pathiv.Derive(relPath, pathiv.PurposeSymlinkIV) // Symlinks are encrypted like file contents and base64-encoded cBinTarget := rfs.contentEnc.EncryptBlockNonce([]byte(plainTarget), 0, nil, nonce) - cTarget := rfs.nameTransform.B64.EncodeToString(cBinTarget) + cTarget := rfs.nameTransform.B64EncodeToString(cBinTarget) // The kernel will reject a symlink target above 4096 chars and return // and I/O error to the user. Better emit the proper error ourselves. if len(cTarget) > syscallcompat.PATH_MAX { diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go index 20fbede0..d5c2c8b6 100644 --- a/internal/nametransform/names.go +++ b/internal/nametransform/names.go @@ -17,6 +17,17 @@ const ( NameMax = 255 ) +// NameTransformer is an interface used to transform filenames. +type NameTransformer interface { + DecryptName(cipherName string, iv []byte) (string, error) + EncryptName(plainName string, iv []byte) string + EncryptAndHashName(name string, iv []byte) (string, error) + HashLongName(name string) string + WriteLongNameAt(dirfd int, hashName string, plainName string) error + B64EncodeToString(src []byte) string + B64DecodeString(s string) ([]byte, error) +} + // NameTransform is used to transform filenames. type NameTransform struct { emeCipher *eme.EMECipher @@ -88,3 +99,13 @@ func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 s cipherName64 = n.B64.EncodeToString(bin) return cipherName64 } + +// B64EncodeToString returns a Base64-encoded string +func (n *NameTransform) B64EncodeToString(src []byte) string { + return n.B64.EncodeToString(src) +} + +// B64DecodeString decodes a Base64-encoded string +func (n *NameTransform) B64DecodeString(s string) ([]byte, error) { + return n.B64.DecodeString(s) +} From 1e74afbb03c06f8cb15b39b3924c20417b3e9448 Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Fri, 8 Mar 2019 18:53:10 -0300 Subject: [PATCH 11/18] Exclude DirIV files if parent directory is excluded. Assuming a directory SOMEDIR (possibly deep in the filesystem hierarchy) is excluded, now SOMEDIR/gocryptfs.diriv is considered excluded. If it were not excluded, it might be possible to infer the existence of SOMEDIR by checking for SOMEDIR/gocryptfs.diriv. In practice I've not been able to actually exploit this, as fuse or the kernel tries to open each directory in a path before actually trying to open (or stat) a file, but it's safer if gocryptfs makes a further check. --- internal/fusefrontend_reverse/excluder.go | 7 ++++++- internal/fusefrontend_reverse/excluder_test.go | 13 ++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/internal/fusefrontend_reverse/excluder.go b/internal/fusefrontend_reverse/excluder.go index d5d91424..5b3cdfcd 100644 --- a/internal/fusefrontend_reverse/excluder.go +++ b/internal/fusefrontend_reverse/excluder.go @@ -65,9 +65,14 @@ func getLines(file string) ([]string, error) { // If relPath is not a special file, it returns the decrypted path or error // from decryptPath for convenience. func (rfs *ReverseFS) isExcludedCipher(relPath string) (bool, string, error) { - if rfs.isTranslatedConfig(relPath) || rfs.isDirIV(relPath) { + if rfs.isTranslatedConfig(relPath) { return false, "", nil } + if rfs.isDirIV(relPath) { + parentDir := nametransform.Dir(relPath) + excluded, _, err := rfs.isExcludedCipher(parentDir) + return excluded, "", err + } if rfs.isNameFile(relPath) { relPath = nametransform.RemoveLongNameSuffix(relPath) } diff --git a/internal/fusefrontend_reverse/excluder_test.go b/internal/fusefrontend_reverse/excluder_test.go index f73defc7..3835ba9f 100644 --- a/internal/fusefrontend_reverse/excluder_test.go +++ b/internal/fusefrontend_reverse/excluder_test.go @@ -100,14 +100,17 @@ func TestShouldNotCallIgnoreParserForTranslatedConfig(t *testing.T) { } } -func TestShouldNotCallIgnoreParserForDirIV(t *testing.T) { +func TestShouldCheckIfParentIsExcludedForDirIV(t *testing.T) { rfs, ignorerMock := createRFSWithMocks() + path := "dir" + ignorerMock.toExclude = "mockdecrypt_dir" + dirIV := path + "/" + nametransform.DirIVFilename - if excluded, _, _ := rfs.isExcludedCipher(nametransform.DirIVFilename); excluded { - t.Error("Should not exclude DirIV") + if excluded, _, _ := rfs.isExcludedCipher(dirIV); !excluded { + t.Error("Should have excluded DirIV based on parent") } - if ignorerMock.calledWith != "" { - t.Error("Should not call IgnoreParser for DirIV") + if ignorerMock.calledWith != "mockdecrypt_dir" { + t.Errorf("Should have checked parent dir, checked %q", ignorerMock.calledWith) } } From eb51879f907187876d7d2bf45ebc44a2d2b9d595 Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Tue, 12 Mar 2019 19:44:35 -0300 Subject: [PATCH 12/18] Fail StatFs if isExcludedCipher returns an error. --- internal/fusefrontend_reverse/rfs.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 340a162a..5675f83b 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -364,12 +364,12 @@ func (rfs *ReverseFS) excludeDirEntries(pDir string, entries []fuse.DirEntry) (f // it's worth, so we just ignore the path and always return info about the // backing storage root dir. func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut { - excluded, _, _ := rfs.isExcludedCipher(relPath) - if excluded { + excluded, _, err := rfs.isExcludedCipher(relPath) + if excluded || err != nil { return nil } var s syscall.Statfs_t - err := syscall.Statfs(rfs.args.Cipherdir, &s) + err = syscall.Statfs(rfs.args.Cipherdir, &s) if err != nil { return nil } From df863e837f21cc2beef400756b610de0d8942d9e Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Thu, 14 Mar 2019 20:24:29 -0300 Subject: [PATCH 13/18] Exclude long name .name files if parent directory is excluded. --- internal/fusefrontend_reverse/excluder.go | 5 +++++ internal/fusefrontend_reverse/excluder_test.go | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/internal/fusefrontend_reverse/excluder.go b/internal/fusefrontend_reverse/excluder.go index 5b3cdfcd..b7815fb3 100644 --- a/internal/fusefrontend_reverse/excluder.go +++ b/internal/fusefrontend_reverse/excluder.go @@ -74,6 +74,11 @@ func (rfs *ReverseFS) isExcludedCipher(relPath string) (bool, string, error) { return excluded, "", err } if rfs.isNameFile(relPath) { + parentDir := nametransform.Dir(relPath) + parentExcluded, _, err := rfs.isExcludedCipher(parentDir) + if parentExcluded || err != nil { + return parentExcluded, "", err + } relPath = nametransform.RemoveLongNameSuffix(relPath) } decPath, err := rfs.decryptPath(relPath) diff --git a/internal/fusefrontend_reverse/excluder_test.go b/internal/fusefrontend_reverse/excluder_test.go index 3835ba9f..95882103 100644 --- a/internal/fusefrontend_reverse/excluder_test.go +++ b/internal/fusefrontend_reverse/excluder_test.go @@ -114,6 +114,20 @@ func TestShouldCheckIfParentIsExcludedForDirIV(t *testing.T) { } } +func TestShouldCheckIfParentIsExcludedForLongName(t *testing.T) { + rfs, ignorerMock := createRFSWithMocks() + path := "parent" + ignorerMock.toExclude = "mockdecrypt_parent" + dirIV := path + "/" + "gocryptfs.longname.fake.name" + + if excluded, _, _ := rfs.isExcludedCipher(dirIV); !excluded { + t.Error("Should have excluded LongName based on parent") + } + if ignorerMock.calledWith != "mockdecrypt_parent" { + t.Errorf("Should have checked parent dir, checked %q", ignorerMock.calledWith) + } +} + func TestShouldDecryptPathAndReturnTrueForExcludedPath(t *testing.T) { rfs, ignorerMock := createRFSWithMocks() ignorerMock.toExclude = "mockdecrypt_file.txt" From e361af61fcdadc988f73042c864a51a9efcf315f Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Thu, 14 Mar 2019 18:53:12 -0300 Subject: [PATCH 14/18] Adds function to detect file type. --- internal/fusefrontend_reverse/rfs.go | 28 ++++++++++++++++ internal/fusefrontend_reverse/rfs_test.go | 40 +++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 internal/fusefrontend_reverse/rfs_test.go diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 5675f83b..c455144e 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -70,6 +70,34 @@ func relDir(path string) string { return dir } +type fileType int + +// Values returned by getFileType +const ( + // A regular file/directory/symlink + regular fileType = iota + // A DirIV (gocryptfs.diriv) file + diriv + // A .name file for a file with a long name + namefile + // The config file + config +) + +// getFileType returns the type of file. Only the name is checked +func (rfs *ReverseFS) getFileType(cPath string) fileType { + if rfs.isDirIV(cPath) { + return diriv + } + if rfs.isNameFile(cPath) { + return namefile + } + if rfs.isTranslatedConfig(cPath) { + return config + } + return regular +} + // isDirIV determines if the path points to a gocryptfs.diriv file func (rfs *ReverseFS) isDirIV(relPath string) bool { if rfs.args.PlaintextNames { diff --git a/internal/fusefrontend_reverse/rfs_test.go b/internal/fusefrontend_reverse/rfs_test.go new file mode 100644 index 00000000..40249ffc --- /dev/null +++ b/internal/fusefrontend_reverse/rfs_test.go @@ -0,0 +1,40 @@ +package fusefrontend_reverse + +import ( + "testing" + + "github.com/rfjakob/gocryptfs/internal/configfile" + "github.com/rfjakob/gocryptfs/internal/nametransform" +) + +func TestShouldDetectDirIV(t *testing.T) { + var rfs ReverseFS + ftype := rfs.getFileType("some/path/" + nametransform.DirIVFilename) + if ftype != diriv { + t.Errorf("Expecting %d, got %d\n", diriv, ftype) + } +} + +func TestShouldDetectNameFile(t *testing.T) { + var rfs ReverseFS + ftype := rfs.getFileType("dir1/dir2/gocryptfs.longname.URrM8kgxTKYMgCk4hKk7RO9Lcfr30XQof4L_5bD9Iro=" + nametransform.LongNameSuffix) + if ftype != namefile { + t.Errorf("Expecting %d, got %d\n", namefile, ftype) + } +} + +func TestShouldDetectConfigFile(t *testing.T) { + var rfs ReverseFS + ftype := rfs.getFileType(configfile.ConfDefaultName) + if ftype != config { + t.Errorf("Expecting %d, got %d\n", config, ftype) + } +} + +func TestShouldDetectRegularFile(t *testing.T) { + var rfs ReverseFS + ftype := rfs.getFileType("documents/text_file.txt") + if ftype != regular { + t.Errorf("Expecting %d, got %d\n", regular, ftype) + } +} From b7a4e072113d14d740839fd2c0d43f092e9bc4a1 Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Fri, 15 Mar 2019 18:38:57 -0300 Subject: [PATCH 15/18] Rename isExcludedCiper to getFileInfo. --- internal/fusefrontend_reverse/excluder.go | 27 ------ .../fusefrontend_reverse/excluder_test.go | 82 ------------------- internal/fusefrontend_reverse/mocks_test.go | 32 ++++++++ internal/fusefrontend_reverse/rfs.go | 42 ++++++++-- internal/fusefrontend_reverse/rfs_test.go | 53 ++++++++++++ 5 files changed, 121 insertions(+), 115 deletions(-) create mode 100644 internal/fusefrontend_reverse/mocks_test.go diff --git a/internal/fusefrontend_reverse/excluder.go b/internal/fusefrontend_reverse/excluder.go index b7815fb3..5c0941af 100644 --- a/internal/fusefrontend_reverse/excluder.go +++ b/internal/fusefrontend_reverse/excluder.go @@ -7,7 +7,6 @@ import ( "github.com/rfjakob/gocryptfs/internal/exitcodes" "github.com/rfjakob/gocryptfs/internal/fusefrontend" - "github.com/rfjakob/gocryptfs/internal/nametransform" "github.com/rfjakob/gocryptfs/internal/tlog" "github.com/sabhiram/go-gitignore" @@ -60,32 +59,6 @@ func getLines(file string) ([]string, error) { return strings.Split(string(buffer), "\n"), nil } -// isExcludedCipher finds out if relative ciphertext path "relPath" is -// excluded (used when -exclude is passed by the user). -// If relPath is not a special file, it returns the decrypted path or error -// from decryptPath for convenience. -func (rfs *ReverseFS) isExcludedCipher(relPath string) (bool, string, error) { - if rfs.isTranslatedConfig(relPath) { - return false, "", nil - } - if rfs.isDirIV(relPath) { - parentDir := nametransform.Dir(relPath) - excluded, _, err := rfs.isExcludedCipher(parentDir) - return excluded, "", err - } - if rfs.isNameFile(relPath) { - parentDir := nametransform.Dir(relPath) - parentExcluded, _, err := rfs.isExcludedCipher(parentDir) - if parentExcluded || err != nil { - return parentExcluded, "", err - } - relPath = nametransform.RemoveLongNameSuffix(relPath) - } - decPath, err := rfs.decryptPath(relPath) - excluded := err == nil && rfs.isExcludedPlain(decPath) - return excluded, decPath, err -} - // isExcludedPlain finds out if the plaintext path "pPath" is // excluded (used when -exclude is passed by the user). func (rfs *ReverseFS) isExcludedPlain(pPath string) bool { diff --git a/internal/fusefrontend_reverse/excluder_test.go b/internal/fusefrontend_reverse/excluder_test.go index 95882103..e19b99f9 100644 --- a/internal/fusefrontend_reverse/excluder_test.go +++ b/internal/fusefrontend_reverse/excluder_test.go @@ -6,9 +6,7 @@ import ( "reflect" "testing" - "github.com/rfjakob/gocryptfs/internal/configfile" "github.com/rfjakob/gocryptfs/internal/fusefrontend" - "github.com/rfjakob/gocryptfs/internal/nametransform" ) func TestShouldNoCreateExcluderIfNoPattersWereSpecified(t *testing.T) { @@ -69,77 +67,6 @@ func TestShouldReadExcludePatternsFromFiles(t *testing.T) { } } -type IgnoreParserMock struct { - toExclude string - calledWith string -} - -func (parser *IgnoreParserMock) MatchesPath(f string) bool { - parser.calledWith = f - return f == parser.toExclude -} - -type NameTransformMock struct { - nametransform.NameTransform -} - -func (n *NameTransformMock) DecryptName(cipherName string, iv []byte) (string, error) { - return "mockdecrypt_" + cipherName, nil -} - -// Note: See also the integration tests in -// tests/reverse/exclude_test.go -func TestShouldNotCallIgnoreParserForTranslatedConfig(t *testing.T) { - rfs, ignorerMock := createRFSWithMocks() - - if excluded, _, _ := rfs.isExcludedCipher(configfile.ConfDefaultName); excluded { - t.Error("Should not exclude translated config") - } - if ignorerMock.calledWith != "" { - t.Error("Should not call IgnoreParser for translated config") - } -} - -func TestShouldCheckIfParentIsExcludedForDirIV(t *testing.T) { - rfs, ignorerMock := createRFSWithMocks() - path := "dir" - ignorerMock.toExclude = "mockdecrypt_dir" - dirIV := path + "/" + nametransform.DirIVFilename - - if excluded, _, _ := rfs.isExcludedCipher(dirIV); !excluded { - t.Error("Should have excluded DirIV based on parent") - } - if ignorerMock.calledWith != "mockdecrypt_dir" { - t.Errorf("Should have checked parent dir, checked %q", ignorerMock.calledWith) - } -} - -func TestShouldCheckIfParentIsExcludedForLongName(t *testing.T) { - rfs, ignorerMock := createRFSWithMocks() - path := "parent" - ignorerMock.toExclude = "mockdecrypt_parent" - dirIV := path + "/" + "gocryptfs.longname.fake.name" - - if excluded, _, _ := rfs.isExcludedCipher(dirIV); !excluded { - t.Error("Should have excluded LongName based on parent") - } - if ignorerMock.calledWith != "mockdecrypt_parent" { - t.Errorf("Should have checked parent dir, checked %q", ignorerMock.calledWith) - } -} - -func TestShouldDecryptPathAndReturnTrueForExcludedPath(t *testing.T) { - rfs, ignorerMock := createRFSWithMocks() - ignorerMock.toExclude = "mockdecrypt_file.txt" - - if excluded, _, _ := rfs.isExcludedCipher("file.txt"); !excluded { - t.Error("Should have excluded") - } - if ignorerMock.calledWith != "mockdecrypt_file.txt" { - t.Error("Didn't call IgnoreParser with decrypted path") - } -} - func TestShouldReturnFalseIfThereAreNoExclusions(t *testing.T) { var rfs ReverseFS if rfs.isExcludedPlain("any/path") { @@ -155,12 +82,3 @@ func TestShouldCallIgnoreParserToCheckExclusion(t *testing.T) { t.Error("Failed to call IgnoreParser") } } - -func createRFSWithMocks() (*ReverseFS, *IgnoreParserMock) { - ignorerMock := &IgnoreParserMock{} - nameTransformMock := &NameTransformMock{} - var rfs ReverseFS - rfs.excluder = ignorerMock - rfs.nameTransform = nameTransformMock - return &rfs, ignorerMock -} diff --git a/internal/fusefrontend_reverse/mocks_test.go b/internal/fusefrontend_reverse/mocks_test.go new file mode 100644 index 00000000..fd2f940b --- /dev/null +++ b/internal/fusefrontend_reverse/mocks_test.go @@ -0,0 +1,32 @@ +package fusefrontend_reverse + +import ( + "github.com/rfjakob/gocryptfs/internal/nametransform" +) + +type IgnoreParserMock struct { + toExclude string + calledWith string +} + +func (parser *IgnoreParserMock) MatchesPath(f string) bool { + parser.calledWith = f + return f == parser.toExclude +} + +type NameTransformMock struct { + nametransform.NameTransform +} + +func (n *NameTransformMock) DecryptName(cipherName string, iv []byte) (string, error) { + return "mockdecrypt_" + cipherName, nil +} + +func createRFSWithMocks() (*ReverseFS, *IgnoreParserMock) { + ignorerMock := &IgnoreParserMock{} + nameTransformMock := &NameTransformMock{} + var rfs ReverseFS + rfs.excluder = ignorerMock + rfs.nameTransform = nameTransformMock + return &rfs, ignorerMock +} diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index c455144e..074dd846 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -70,6 +70,36 @@ func relDir(path string) string { return dir } +// getFileInfo returns information on a ciphertext path "relPath": +// - excluded: if the path is excluded +// - pPath: if it's not a special file, the decrypted path +// - err: non nil if any error happens +func (rfs *ReverseFS) getFileInfo(relPath string) (excluded bool, pPath string, err error) { + if rfs.isTranslatedConfig(relPath) { + excluded, pPath, err = false, "", nil + return + } + if rfs.isDirIV(relPath) { + parentDir := nametransform.Dir(relPath) + excluded, _, err = rfs.getFileInfo(parentDir) + pPath = "" + return + } + if rfs.isNameFile(relPath) { + parentDir := nametransform.Dir(relPath) + var parentExcluded bool + parentExcluded, _, err = rfs.getFileInfo(parentDir) + if parentExcluded || err != nil { + excluded, pPath = parentExcluded, "" + return + } + relPath = nametransform.RemoveLongNameSuffix(relPath) + } + pPath, err = rfs.decryptPath(relPath) + excluded = err == nil && rfs.isExcludedPlain(pPath) + return +} + type fileType int // Values returned by getFileType @@ -133,7 +163,7 @@ func (rfs *ReverseFS) isTranslatedConfig(relPath string) bool { // GetAttr - FUSE call // "relPath" is the relative ciphertext path func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) { - excluded, pPath, err := rfs.isExcludedCipher(relPath) + excluded, pPath, err := rfs.getFileInfo(relPath) if excluded { return nil, fuse.ENOENT } @@ -221,7 +251,7 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr // Access - FUSE call func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) fuse.Status { - excluded, pPath, err := rfs.isExcludedCipher(relPath) + excluded, pPath, err := rfs.getFileInfo(relPath) if excluded { return fuse.ENOENT } @@ -248,7 +278,7 @@ func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) // Open - FUSE call func (rfs *ReverseFS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { - excluded, pPath, err := rfs.isExcludedCipher(relPath) + excluded, pPath, err := rfs.getFileInfo(relPath) if excluded { return nil, fuse.ENOENT } @@ -294,7 +324,7 @@ func (rfs *ReverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEn // OpenDir - FUSE readdir call func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { - excluded, relPath, err := rfs.isExcludedCipher(cipherPath) + excluded, relPath, err := rfs.getFileInfo(cipherPath) if excluded { return nil, fuse.ENOENT } @@ -392,7 +422,7 @@ func (rfs *ReverseFS) excludeDirEntries(pDir string, entries []fuse.DirEntry) (f // it's worth, so we just ignore the path and always return info about the // backing storage root dir. func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut { - excluded, _, err := rfs.isExcludedCipher(relPath) + excluded, _, err := rfs.getFileInfo(relPath) if excluded || err != nil { return nil } @@ -408,7 +438,7 @@ func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut { // Readlink - FUSE call func (rfs *ReverseFS) Readlink(relPath string, context *fuse.Context) (string, fuse.Status) { - excluded, pPath, err := rfs.isExcludedCipher(relPath) + excluded, pPath, err := rfs.getFileInfo(relPath) if excluded { return "", fuse.ENOENT } diff --git a/internal/fusefrontend_reverse/rfs_test.go b/internal/fusefrontend_reverse/rfs_test.go index 40249ffc..a57234d5 100644 --- a/internal/fusefrontend_reverse/rfs_test.go +++ b/internal/fusefrontend_reverse/rfs_test.go @@ -38,3 +38,56 @@ func TestShouldDetectRegularFile(t *testing.T) { t.Errorf("Expecting %d, got %d\n", regular, ftype) } } + +// Note: For path exclusion, see also the integration tests in +// tests/reverse/exclude_test.go +func TestShouldNotCallIgnoreParserForTranslatedConfig(t *testing.T) { + rfs, ignorerMock := createRFSWithMocks() + + if excluded, _, _ := rfs.getFileInfo(configfile.ConfDefaultName); excluded { + t.Error("Should not exclude translated config") + } + if ignorerMock.calledWith != "" { + t.Error("Should not call IgnoreParser for translated config") + } +} + +func TestShouldCheckIfParentIsExcludedForDirIV(t *testing.T) { + rfs, ignorerMock := createRFSWithMocks() + path := "dir" + ignorerMock.toExclude = "mockdecrypt_dir" + dirIV := path + "/" + nametransform.DirIVFilename + + if excluded, _, _ := rfs.getFileInfo(dirIV); !excluded { + t.Error("Should have excluded DirIV based on parent") + } + if ignorerMock.calledWith != "mockdecrypt_dir" { + t.Errorf("Should have checked parent dir, checked %q", ignorerMock.calledWith) + } +} + +func TestShouldCheckIfParentIsExcludedForLongName(t *testing.T) { + rfs, ignorerMock := createRFSWithMocks() + path := "parent" + ignorerMock.toExclude = "mockdecrypt_parent" + dirIV := path + "/" + "gocryptfs.longname.fake.name" + + if excluded, _, _ := rfs.getFileInfo(dirIV); !excluded { + t.Error("Should have excluded LongName based on parent") + } + if ignorerMock.calledWith != "mockdecrypt_parent" { + t.Errorf("Should have checked parent dir, checked %q", ignorerMock.calledWith) + } +} + +func TestShouldDecryptPathAndReturnTrueForExcludedPath(t *testing.T) { + rfs, ignorerMock := createRFSWithMocks() + ignorerMock.toExclude = "mockdecrypt_file.txt" + + if excluded, _, _ := rfs.getFileInfo("file.txt"); !excluded { + t.Error("Should have excluded") + } + if ignorerMock.calledWith != "mockdecrypt_file.txt" { + t.Error("Didn't call IgnoreParser with decrypted path") + } +} From 68d7a2b6c8940c971f3178b6d4941772d792ecab Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Fri, 15 Mar 2019 19:34:44 -0300 Subject: [PATCH 16/18] getFileInfo returns the file type. --- internal/fusefrontend_reverse/rfs.go | 26 ++++++++------- internal/fusefrontend_reverse/rfs_test.go | 39 ++++++++++++++++++++--- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 074dd846..ee73fea8 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -71,24 +71,26 @@ func relDir(path string) string { } // getFileInfo returns information on a ciphertext path "relPath": +// - ftype: file type (as returned by getFileType) // - excluded: if the path is excluded // - pPath: if it's not a special file, the decrypted path // - err: non nil if any error happens -func (rfs *ReverseFS) getFileInfo(relPath string) (excluded bool, pPath string, err error) { - if rfs.isTranslatedConfig(relPath) { +func (rfs *ReverseFS) getFileInfo(relPath string) (ftype fileType, excluded bool, pPath string, err error) { + ftype = rfs.getFileType(relPath) + if ftype == config { excluded, pPath, err = false, "", nil return } - if rfs.isDirIV(relPath) { + if ftype == diriv { parentDir := nametransform.Dir(relPath) - excluded, _, err = rfs.getFileInfo(parentDir) + _, excluded, _, err = rfs.getFileInfo(parentDir) pPath = "" return } - if rfs.isNameFile(relPath) { + if ftype == namefile { parentDir := nametransform.Dir(relPath) var parentExcluded bool - parentExcluded, _, err = rfs.getFileInfo(parentDir) + _, parentExcluded, _, err = rfs.getFileInfo(parentDir) if parentExcluded || err != nil { excluded, pPath = parentExcluded, "" return @@ -163,7 +165,7 @@ func (rfs *ReverseFS) isTranslatedConfig(relPath string) bool { // GetAttr - FUSE call // "relPath" is the relative ciphertext path func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) { - excluded, pPath, err := rfs.getFileInfo(relPath) + _, excluded, pPath, err := rfs.getFileInfo(relPath) if excluded { return nil, fuse.ENOENT } @@ -251,7 +253,7 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr // Access - FUSE call func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) fuse.Status { - excluded, pPath, err := rfs.getFileInfo(relPath) + _, excluded, pPath, err := rfs.getFileInfo(relPath) if excluded { return fuse.ENOENT } @@ -278,7 +280,7 @@ func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) // Open - FUSE call func (rfs *ReverseFS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { - excluded, pPath, err := rfs.getFileInfo(relPath) + _, excluded, pPath, err := rfs.getFileInfo(relPath) if excluded { return nil, fuse.ENOENT } @@ -324,7 +326,7 @@ func (rfs *ReverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEn // OpenDir - FUSE readdir call func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { - excluded, relPath, err := rfs.getFileInfo(cipherPath) + _, excluded, relPath, err := rfs.getFileInfo(cipherPath) if excluded { return nil, fuse.ENOENT } @@ -422,7 +424,7 @@ func (rfs *ReverseFS) excludeDirEntries(pDir string, entries []fuse.DirEntry) (f // it's worth, so we just ignore the path and always return info about the // backing storage root dir. func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut { - excluded, _, err := rfs.getFileInfo(relPath) + _, excluded, _, err := rfs.getFileInfo(relPath) if excluded || err != nil { return nil } @@ -438,7 +440,7 @@ func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut { // Readlink - FUSE call func (rfs *ReverseFS) Readlink(relPath string, context *fuse.Context) (string, fuse.Status) { - excluded, pPath, err := rfs.getFileInfo(relPath) + _, excluded, pPath, err := rfs.getFileInfo(relPath) if excluded { return "", fuse.ENOENT } diff --git a/internal/fusefrontend_reverse/rfs_test.go b/internal/fusefrontend_reverse/rfs_test.go index a57234d5..b6af11c1 100644 --- a/internal/fusefrontend_reverse/rfs_test.go +++ b/internal/fusefrontend_reverse/rfs_test.go @@ -44,7 +44,14 @@ func TestShouldDetectRegularFile(t *testing.T) { func TestShouldNotCallIgnoreParserForTranslatedConfig(t *testing.T) { rfs, ignorerMock := createRFSWithMocks() - if excluded, _, _ := rfs.getFileInfo(configfile.ConfDefaultName); excluded { + ftype, excluded, _, err := rfs.getFileInfo(configfile.ConfDefaultName) + if err != nil { + t.Errorf("Unexpected error %q\n", err) + } + if ftype != config { + t.Errorf("Wrong file type, expecting %d, got %d\n", config, ftype) + } + if excluded { t.Error("Should not exclude translated config") } if ignorerMock.calledWith != "" { @@ -58,7 +65,14 @@ func TestShouldCheckIfParentIsExcludedForDirIV(t *testing.T) { ignorerMock.toExclude = "mockdecrypt_dir" dirIV := path + "/" + nametransform.DirIVFilename - if excluded, _, _ := rfs.getFileInfo(dirIV); !excluded { + ftype, excluded, _, err := rfs.getFileInfo(dirIV) + if err != nil { + t.Errorf("Unexpected error %q\n", err) + } + if ftype != diriv { + t.Errorf("Wrong file type, expecting %d, got %d\n", diriv, ftype) + } + if !excluded { t.Error("Should have excluded DirIV based on parent") } if ignorerMock.calledWith != "mockdecrypt_dir" { @@ -72,7 +86,14 @@ func TestShouldCheckIfParentIsExcludedForLongName(t *testing.T) { ignorerMock.toExclude = "mockdecrypt_parent" dirIV := path + "/" + "gocryptfs.longname.fake.name" - if excluded, _, _ := rfs.getFileInfo(dirIV); !excluded { + ftype, excluded, _, err := rfs.getFileInfo(dirIV) + if err != nil { + t.Errorf("Unexpected error %q\n", err) + } + if ftype != namefile { + t.Errorf("Wrong file type, expecting %d, got %d\n", namefile, ftype) + } + if !excluded { t.Error("Should have excluded LongName based on parent") } if ignorerMock.calledWith != "mockdecrypt_parent" { @@ -84,9 +105,19 @@ func TestShouldDecryptPathAndReturnTrueForExcludedPath(t *testing.T) { rfs, ignorerMock := createRFSWithMocks() ignorerMock.toExclude = "mockdecrypt_file.txt" - if excluded, _, _ := rfs.getFileInfo("file.txt"); !excluded { + ftype, excluded, pPath, err := rfs.getFileInfo("file.txt") + if err != nil { + t.Errorf("Unexpected error %q\n", err) + } + if ftype != regular { + t.Errorf("Wrong file type, expecting %d, got %d\n", regular, ftype) + } + if !excluded { t.Error("Should have excluded") } + if pPath != "mockdecrypt_file.txt" { + t.Errorf("Wrong pPath returned, got %q\n", pPath) + } if ignorerMock.calledWith != "mockdecrypt_file.txt" { t.Error("Didn't call IgnoreParser with decrypted path") } From 875326aa98a1adc3ea394fd0b5569285e10f409b Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Sat, 16 Mar 2019 10:11:52 -0300 Subject: [PATCH 17/18] Use file type returned by getFileInfo in GetAttr, Access and Open. --- internal/fusefrontend_reverse/rfs.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index ee73fea8..58299fff 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -165,7 +165,7 @@ func (rfs *ReverseFS) isTranslatedConfig(relPath string) bool { // GetAttr - FUSE call // "relPath" is the relative ciphertext path func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) { - _, excluded, pPath, err := rfs.getFileInfo(relPath) + ftype, excluded, pPath, err := rfs.getFileInfo(relPath) if excluded { return nil, fuse.ENOENT } @@ -173,7 +173,7 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr return nil, fuse.ToStatus(err) } // Handle "gocryptfs.conf" - if rfs.isTranslatedConfig(relPath) { + if ftype == config { absConfPath, _ := rfs.abs(configfile.ConfReverseName, nil) var st syscall.Stat_t err = syscall.Lstat(absConfPath, &st) @@ -191,11 +191,11 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr var f nodefs.File var status fuse.Status virtual := false - if rfs.isDirIV(relPath) { + if ftype == diriv { virtual = true f, status = rfs.newDirIVFile(relPath) } - if rfs.isNameFile(relPath) { + if ftype == namefile { virtual = true f, status = rfs.newNameFile(relPath) } @@ -253,14 +253,14 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr // Access - FUSE call func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) fuse.Status { - _, excluded, pPath, err := rfs.getFileInfo(relPath) + ftype, excluded, pPath, err := rfs.getFileInfo(relPath) if excluded { return fuse.ENOENT } if err != nil { return fuse.ToStatus(err) } - if rfs.isTranslatedConfig(relPath) || rfs.isDirIV(relPath) || rfs.isNameFile(relPath) { + if ftype != regular { // access(2) R_OK flag for checking if the file is readable, always 4 as defined in POSIX. ROK := uint32(0x4) // Virtual files can always be read and never written @@ -280,20 +280,20 @@ func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) // Open - FUSE call func (rfs *ReverseFS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { - _, excluded, pPath, err := rfs.getFileInfo(relPath) + ftype, excluded, pPath, err := rfs.getFileInfo(relPath) if excluded { return nil, fuse.ENOENT } if err != nil { return nil, fuse.ToStatus(err) } - if rfs.isTranslatedConfig(relPath) { + if ftype == config { return rfs.loopbackfs.Open(configfile.ConfReverseName, flags, context) } - if rfs.isDirIV(relPath) { + if ftype == diriv { return rfs.newDirIVFile(relPath) } - if rfs.isNameFile(relPath) { + if ftype == namefile { return rfs.newNameFile(relPath) } return rfs.newFile(relPath, pPath) From 8b203fac8ea66b61c89a2e4214e2fabdfd45e7ea Mon Sep 17 00:00:00 2001 From: Eduardo M KALINOWSKI Date: Sat, 16 Mar 2019 11:02:24 -0300 Subject: [PATCH 18/18] Fail OpenDir and Readlink if target is a virtual file. --- internal/fusefrontend_reverse/rfs.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 58299fff..b85a10f5 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -326,13 +326,16 @@ func (rfs *ReverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEn // OpenDir - FUSE readdir call func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { - _, excluded, relPath, err := rfs.getFileInfo(cipherPath) + ftype, excluded, relPath, err := rfs.getFileInfo(cipherPath) if excluded { return nil, fuse.ENOENT } if err != nil { return nil, fuse.ToStatus(err) } + if ftype != regular { + return nil, fuse.ENOTDIR + } // Read plaintext dir dirfd, err := syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, filepath.Dir(relPath)) if err != nil { @@ -440,13 +443,16 @@ func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut { // Readlink - FUSE call func (rfs *ReverseFS) Readlink(relPath string, context *fuse.Context) (string, fuse.Status) { - _, excluded, pPath, err := rfs.getFileInfo(relPath) + ftype, excluded, pPath, err := rfs.getFileInfo(relPath) if excluded { return "", fuse.ENOENT } if err != nil { return "", fuse.ToStatus(err) } + if ftype != regular { + return "", fuse.EINVAL + } dirfd, name, err := rfs.openBackingDir(pPath) if err != nil { return "", fuse.ToStatus(err)