From ec2fdc19cf9358ae7ba09c528a5807b6b0760f9b Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sat, 11 Aug 2018 23:26:49 +0200 Subject: reverse mode: add --exclude option https://github.com/rfjakob/gocryptfs/issues/235 --- internal/fusefrontend_reverse/rfile.go | 6 +++ internal/fusefrontend_reverse/rfs.go | 74 +++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) (limited to 'internal/fusefrontend_reverse') diff --git a/internal/fusefrontend_reverse/rfile.go b/internal/fusefrontend_reverse/rfile.go index 26756a3..7df0906 100644 --- a/internal/fusefrontend_reverse/rfile.go +++ b/internal/fusefrontend_reverse/rfile.go @@ -36,6 +36,12 @@ var inodeTable syncmap.Map // newFile decrypts and opens the path "relPath" 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) { + // 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) diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 829983b..ba8810c 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -2,7 +2,9 @@ package fusefrontend_reverse import ( "fmt" + "os" "path/filepath" + "strings" "syscall" "golang.org/x/sys/unix" @@ -14,6 +16,8 @@ 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" @@ -34,6 +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. + cExclude []string } var _ pathfs.FileSystem = &ReverseFS{} @@ -43,7 +49,7 @@ var _ pathfs.FileSystem = &ReverseFS{} // ReverseFS provides an encrypted view. func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *ReverseFS { initLongnameCache() - return &ReverseFS{ + fs := &ReverseFS{ // pathfs.defaultFileSystem returns ENOSYS for all operations FileSystem: pathfs.NewDefaultFileSystem(), loopbackfs: pathfs.NewLoopbackFileSystem(args.Cipherdir), @@ -51,6 +57,22 @@ func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.Na nameTransform: n, 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) + } + 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) + } + tlog.Debug.Printf("-exclude: %v -> %v", fs.args.Exclude, fs.cExclude) + } + return fs } // relDir is identical to filepath.Dir excepts that it returns "" when @@ -64,6 +86,21 @@ 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 { + for _, e := range rfs.cExclude { + if e == relPath { + return true + } + // Files inside an excluded directory are also excluded + if strings.HasPrefix(relPath, e+"/") { + return true + } + } + return false +} + // isDirIV determines if the path points to a gocryptfs.diriv file func (rfs *ReverseFS) isDirIV(relPath string) bool { if rfs.args.PlaintextNames { @@ -99,6 +136,9 @@ 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) { + return nil, fuse.ENOENT + } // Handle "gocryptfs.conf" if rfs.isTranslatedConfig(relPath) { absConfPath, _ := rfs.abs(configfile.ConfReverseName, nil) @@ -180,6 +220,9 @@ 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) { + return fuse.ENOENT + } 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) @@ -203,6 +246,9 @@ 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) { + return nil, fuse.ENOENT + } if rfs.isTranslatedConfig(relPath) { return rfs.loopbackfs.Open(configfile.ConfReverseName, flags, context) } @@ -242,6 +288,9 @@ 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) { + return nil, fuse.ENOENT + } relPath, err := rfs.decryptPath(cipherPath) if err != nil { return nil, fuse.ToStatus(err) @@ -292,6 +341,21 @@ func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse. } entries[i].Name = cName } + // Filter out excluded entries + if rfs.cExclude != nil { + 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(cipherPath, entry.Name) + if rfs.isExcluded(p) { + // Skip file + continue + } + filtered = append(filtered, entry) + } + entries = filtered + } entries = append(entries, virtualFiles[:nVirtual]...) return entries, fuse.OK } @@ -301,7 +365,10 @@ func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse. // Securing statfs against symlink races seems to be more trouble than // it's worth, so we just ignore the path and always return info about the // backing storage root dir. -func (rfs *ReverseFS) StatFs(path string) *fuse.StatfsOut { +func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut { + if rfs.isExcluded(relPath) { + return nil + } var s syscall.Statfs_t err := syscall.Statfs(rfs.args.Cipherdir, &s) if err != nil { @@ -314,6 +381,9 @@ func (rfs *ReverseFS) StatFs(path string) *fuse.StatfsOut { // Readlink - FUSE call func (rfs *ReverseFS) Readlink(relPath string, context *fuse.Context) (string, fuse.Status) { + if rfs.isExcluded(relPath) { + return "", fuse.ENOENT + } dirfd, name, err := rfs.openBackingDir(relPath) if err != nil { return "", fuse.ToStatus(err) -- cgit v1.2.3