diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/fusefrontend_reverse/excluder.go | 6 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/excluder_test.go | 8 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/node.go | 33 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/node_dir_ops.go | 17 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/node_helpers.go | 51 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/root_node.go | 37 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rpath.go | 13 | 
7 files changed, 104 insertions, 61 deletions
| diff --git a/internal/fusefrontend_reverse/excluder.go b/internal/fusefrontend_reverse/excluder.go index b6cb961..8a13fa7 100644 --- a/internal/fusefrontend_reverse/excluder.go +++ b/internal/fusefrontend_reverse/excluder.go @@ -2,6 +2,7 @@ package fusefrontend_reverse  import (  	"io/ioutil" +	"log"  	"os"  	"strings" @@ -15,12 +16,9 @@ import (  // prepareExcluder creates an object to check if paths are excluded  // based on the patterns specified in the command line.  func prepareExcluder(args fusefrontend.Args) *ignore.GitIgnore { -	if len(args.Exclude) == 0 && len(args.ExcludeWildcard) == 0 && len(args.ExcludeFrom) == 0 { -		return nil -	}  	patterns := getExclusionPatterns(args)  	if len(patterns) == 0 { -		panic(patterns) +		log.Panic(patterns)  	}  	excluder, err := ignore.CompileIgnoreLines(patterns...)  	if err != nil { diff --git a/internal/fusefrontend_reverse/excluder_test.go b/internal/fusefrontend_reverse/excluder_test.go index 47b430a..d6cfef3 100644 --- a/internal/fusefrontend_reverse/excluder_test.go +++ b/internal/fusefrontend_reverse/excluder_test.go @@ -9,14 +9,6 @@ import (  	"github.com/rfjakob/gocryptfs/internal/fusefrontend"  ) -func TestShouldNoCreateExcluderIfNoPattersWereSpecified(t *testing.T) { -	var args fusefrontend.Args -	excluder := prepareExcluder(args) -	if excluder != nil { -		t.Error("Should not have created excluder") -	} -} -  func TestShouldPrefixExcludeValuesWithSlash(t *testing.T) {  	var args fusefrontend.Args  	args.Exclude = []string{"file1", "dir1/file2.txt"} diff --git a/internal/fusefrontend_reverse/node.go b/internal/fusefrontend_reverse/node.go index deaf953..de1ee49 100644 --- a/internal/fusefrontend_reverse/node.go +++ b/internal/fusefrontend_reverse/node.go @@ -26,8 +26,7 @@ type Node struct {  // Lookup - FUSE call for discovering a file.  func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { -	dirfd := int(-1) -	pName := "" +	var d *dirfdPlus  	t := n.lookupFileType(cName)  	if t == typeDiriv {  		// gocryptfs.diriv @@ -40,14 +39,15 @@ func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch  		return n.lookupConf(ctx, out)  	} else if t == typeReal {  		// real file -		dirfd, pName, errno = n.prepareAtSyscall(cName) +		d, errno = n.prepareAtSyscall(cName) +		//fmt.Printf("Lookup: prepareAtSyscall -> d=%#v, errno=%d\n", d, errno)  		if errno != 0 {  			return  		} -		defer syscall.Close(dirfd) +		defer syscall.Close(d.dirfd)  	}  	// Get device number and inode number into `st` -	st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) +	st, err := syscallcompat.Fstatat2(d.dirfd, d.pName, unix.AT_SYMLINK_NOFOLLOW)  	if err != nil {  		return nil, fs.ToErrno(err)  	} @@ -55,7 +55,7 @@ func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch  	ch = n.newChild(ctx, st, out)  	// Translate ciphertext size in `out.Attr.Size` to plaintext size  	if t == typeReal { -		n.translateSize(dirfd, cName, pName, &out.Attr) +		n.translateSize(d.dirfd, cName, d.pName, &out.Attr)  	}  	return ch, 0  } @@ -69,13 +69,13 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)  		return f.(fs.FileGetattrer).Getattr(ctx, out)  	} -	dirfd, pName, errno := n.prepareAtSyscall("") +	d, errno := n.prepareAtSyscall("")  	if errno != 0 {  		return  	} -	defer syscall.Close(dirfd) +	defer syscall.Close(d.dirfd) -	st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) +	st, err := syscallcompat.Fstatat2(d.dirfd, d.pName, unix.AT_SYMLINK_NOFOLLOW)  	if err != nil {  		return fs.ToErrno(err)  	} @@ -87,7 +87,7 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)  	// Translate ciphertext size in `out.Attr.Size` to plaintext size  	cName := filepath.Base(n.Path()) -	n.translateSize(dirfd, cName, pName, &out.Attr) +	n.translateSize(d.dirfd, cName, d.pName, &out.Attr)  	if rn.args.ForceOwner != nil {  		out.Owner = *rn.args.ForceOwner @@ -99,27 +99,26 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)  //  // Symlink-safe through openBackingDir() + Readlinkat().  func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) { -	dirfd, pName, errno := n.prepareAtSyscall("") +	d, errno := n.prepareAtSyscall("")  	if errno != 0 {  		return  	} -	defer syscall.Close(dirfd) +	defer syscall.Close(d.dirfd) -	cName := filepath.Base(n.Path()) -	return n.readlink(dirfd, cName, pName) +	return n.readlink(d.dirfd, d.cName, d.pName)  }  // Open - FUSE call. Open already-existing file.  //  // Symlink-safe through Openat().  func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { -	dirfd, pName, errno := n.prepareAtSyscall("") +	d, errno := n.prepareAtSyscall("")  	if errno != 0 {  		return  	} -	defer syscall.Close(dirfd) +	defer syscall.Close(d.dirfd) -	fd, err := syscallcompat.Openat(dirfd, pName, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0) +	fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)  	if err != nil {  		errno = fs.ToErrno(err)  		return diff --git a/internal/fusefrontend_reverse/node_dir_ops.go b/internal/fusefrontend_reverse/node_dir_ops.go index 5ec1e95..22f8122 100644 --- a/internal/fusefrontend_reverse/node_dir_ops.go +++ b/internal/fusefrontend_reverse/node_dir_ops.go @@ -23,15 +23,15 @@ import (  // This function is symlink-safe through use of openBackingDir() and  // ReadDirIVAt().  func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.Errno) { -	dirfd, cName, errno := n.prepareAtSyscall("") +	d, errno := n.prepareAtSyscall("")  	if errno != 0 {  		return  	} -	defer syscall.Close(dirfd) +	defer syscall.Close(d.dirfd)  	// Read plaintext directory  	var entries []fuse.DirEntry -	fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) +	fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)  	if err != nil {  		return nil, fs.ToErrno(err)  	} @@ -42,21 +42,20 @@ func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.  	}  	rn := n.rootNode() + +	// Filter out excluded entries +	entries = rn.excludeDirEntries(d, entries) +  	if rn.args.PlaintextNames {  		return n.readdirPlaintextnames(entries)  	} -	// Filter out excluded entries -	//TODO -	//entries = rfs.excludeDirEntries(relPath, entries) -  	// Virtual files: at least one gocryptfs.diriv file  	virtualFiles := []fuse.DirEntry{  		{Mode: virtualFileMode, Name: nametransform.DirIVFilename},  	} -	cipherPath := n.Path() -	dirIV := pathiv.Derive(cipherPath, pathiv.PurposeDirIV) +	dirIV := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)  	// Encrypt names  	for i := range entries {  		var cName string diff --git a/internal/fusefrontend_reverse/node_helpers.go b/internal/fusefrontend_reverse/node_helpers.go index 76ddd42..92f6a87 100644 --- a/internal/fusefrontend_reverse/node_helpers.go +++ b/internal/fusefrontend_reverse/node_helpers.go @@ -47,6 +47,20 @@ func (n *Node) rootNode() *RootNode {  	return n.Root().Operations().(*RootNode)  } +// dirfdPlus gets filled out as we gather information about a node +type dirfdPlus struct { +	// fd to the directory, opened with O_DIRECTORY|O_PATH +	dirfd int +	// Relative plaintext path +	pPath string +	// Plaintext basename: filepath.Base(pPath) +	pName string +	// Relative ciphertext path +	cPath string +	// Ciphertext basename: filepath.Base(cPath) +	cName string +} +  // prepareAtSyscall returns a (dirfd, cName) pair that can be used  // with the "___at" family of system calls (openat, fstatat, unlinkat...) to  // access the backing encrypted directory. @@ -54,16 +68,23 @@ func (n *Node) rootNode() *RootNode {  // If you pass a `child` file name, the (dirfd, cName) pair will refer to  // a child of this node.  // If `child` is empty, the (dirfd, cName) pair refers to this node itself. -func (n *Node) prepareAtSyscall(child string) (dirfd int, pName string, errno syscall.Errno) { -	p := n.Path() +func (n *Node) prepareAtSyscall(child string) (d *dirfdPlus, errno syscall.Errno) { +	cPath := n.Path()  	if child != "" { -		p = filepath.Join(p, child) +		cPath = filepath.Join(cPath, child)  	}  	rn := n.rootNode() -	dirfd, pName, err := rn.openBackingDir(p) +	dirfd, pPath, err := rn.openBackingDir(cPath)  	if err != nil {  		errno = fs.ToErrno(err)  	} +	d = &dirfdPlus{ +		dirfd: dirfd, +		pPath: pPath, +		pName: filepath.Base(pPath), +		cPath: cPath, +		cName: filepath.Base(cPath), +	}  	return  } @@ -91,28 +112,32 @@ func (n *Node) isRoot() bool {  }  func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { -	dirfd, pName1, errno := n.prepareAtSyscall("") +	d, errno := n.prepareAtSyscall("")  	if errno != 0 {  		return  	} -	defer syscall.Close(dirfd) +	defer syscall.Close(d.dirfd)  	// Find the file the gocryptfs.longname.XYZ.name file belongs to in the  	// directory listing -	fd, err := syscallcompat.Openat(dirfd, pName1, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) +	fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)  	if err != nil {  		errno = fs.ToErrno(err)  		return  	}  	defer syscall.Close(fd) -	diriv := pathiv.Derive(n.Path(), pathiv.PurposeDirIV) +	diriv := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)  	rn := n.rootNode()  	pName, cFullname, errno := rn.findLongnameParent(fd, diriv, nameFile)  	if errno != 0 {  		return  	} +	if rn.isExcludedPlain(filepath.Join(d.cPath, pName)) { +		errno = syscall.EPERM +		return +	}  	// Get attrs from parent file -	st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) +	st, err := syscallcompat.Fstatat2(fd, pName, unix.AT_SYMLINK_NOFOLLOW)  	if err != nil {  		errno = fs.ToErrno(err)  		return @@ -132,17 +157,17 @@ func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fus  // lookupDiriv returns a new Inode for a gocryptfs.diriv file inside `n`.  func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { -	dirfd, pName, errno := n.prepareAtSyscall("") +	d, errno := n.prepareAtSyscall("")  	if errno != 0 {  		return  	} -	defer syscall.Close(dirfd) -	st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) +	defer syscall.Close(d.dirfd) +	st, err := syscallcompat.Fstatat2(d.dirfd, d.pName, unix.AT_SYMLINK_NOFOLLOW)  	if err != nil {  		errno = fs.ToErrno(err)  		return  	} -	content := pathiv.Derive(n.Path(), pathiv.PurposeDirIV) +	content := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)  	var vf *VirtualMemNode  	vf, errno = n.newVirtualMemNode(content, st, inoTagDirIV)  	if errno != 0 { diff --git a/internal/fusefrontend_reverse/root_node.go b/internal/fusefrontend_reverse/root_node.go index 4346306..b7a259a 100644 --- a/internal/fusefrontend_reverse/root_node.go +++ b/internal/fusefrontend_reverse/root_node.go @@ -2,12 +2,16 @@ package fusefrontend_reverse  import (  	"log" +	"path/filepath"  	"strings"  	"syscall" +	"github.com/rfjakob/gocryptfs/internal/tlog" +  	"golang.org/x/sys/unix"  	"github.com/hanwen/go-fuse/v2/fs" +	"github.com/hanwen/go-fuse/v2/fuse"  	"github.com/rfjakob/gocryptfs/internal/contentenc"  	"github.com/rfjakob/gocryptfs/internal/fusefrontend" @@ -28,7 +32,7 @@ type RootNode struct {  	// Content encryption helper  	contentEnc *contentenc.ContentEnc  	// Tests whether a path is excluded (hidden) from the user. Used by -exclude. -	excluder *ignore.GitIgnore +	excluder ignore.IgnoreParser  	// inoMap translates inode numbers from different devices to unique inode  	// numbers.  	inoMap *inomap.InoMap @@ -38,17 +42,23 @@ type RootNode struct {  // In this case (reverse mode) the backing directory is plain-text and  // ReverseFS provides an encrypted view.  func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *RootNode { -	return &RootNode{ +	rn := &RootNode{  		args:          args,  		nameTransform: n,  		contentEnc:    c,  		inoMap:        inomap.New(), -		excluder:      prepareExcluder(args),  	} +	if len(args.Exclude) > 0 || len(args.ExcludeWildcard) > 0 || len(args.ExcludeFrom) > 0 { +		rn.excluder = prepareExcluder(args) +	} +	return rn  }  // You can pass either gocryptfs.longname.XYZ.name or gocryptfs.longname.XYZ.  func (rn *RootNode) findLongnameParent(fd int, diriv []byte, longname string) (pName string, cFullName string, errno syscall.Errno) { +	defer func() { +		tlog.Debug.Printf("findLongnameParent: %d %x %q -> %q %q %d\n", fd, diriv, longname, pName, cFullName, errno) +	}()  	if strings.HasSuffix(longname, nametransform.LongNameSuffix) {  		longname = nametransform.RemoveLongNameSuffix(longname)  	} @@ -84,3 +94,24 @@ func (rn *RootNode) findLongnameParent(fd int, diriv []byte, longname string) (p  func (rn *RootNode) isExcludedPlain(pPath string) bool {  	return rn.excluder != nil && rn.excluder.MatchesPath(pPath)  } + +// excludeDirEntries filters out directory entries that are "-exclude"d. +// pDir is the relative plaintext path to the directory these entries are +// from. The entries should be plaintext files. +func (rn *RootNode) excludeDirEntries(d *dirfdPlus, entries []fuse.DirEntry) (filtered []fuse.DirEntry) { +	if rn.excluder == nil { +		return entries +	} +	filtered = make([]fuse.DirEntry, 0, len(entries)) +	for _, entry := range entries { +		// filepath.Join handles the case of pDir="" correctly: +		// Join("", "foo") -> "foo". This does not: pDir + "/" + name" +		p := filepath.Join(d.pPath, entry.Name) +		if rn.isExcludedPlain(p) { +			// Skip file +			continue +		} +		filtered = append(filtered, entry) +	} +	return filtered +} diff --git a/internal/fusefrontend_reverse/rpath.go b/internal/fusefrontend_reverse/rpath.go index 1e44638..f29bbf5 100644 --- a/internal/fusefrontend_reverse/rpath.go +++ b/internal/fusefrontend_reverse/rpath.go @@ -100,25 +100,24 @@ func (rn *RootNode) decryptPath(cPath string) (string, error) {  // 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 (rn *RootNode) openBackingDir(cPath string) (dirfd int, pName string, err error) { +func (rn *RootNode) openBackingDir(cPath string) (dirfd int, pPath string, err error) {  	defer func() { -		tlog.Debug.Printf("openBackingDir %q -> %d %q %v\n", cPath, dirfd, pName, err) +		tlog.Debug.Printf("openBackingDir %q -> %d %q %v\n", cPath, dirfd, pPath, err)  	}()  	dirfd = -1 -	pRelPath, err := rn.decryptPath(cPath) +	pPath, err = rn.decryptPath(cPath)  	if err != nil {  		return  	} -	if rn.isExcludedPlain(pRelPath) { +	if rn.isExcludedPlain(pPath) {  		err = syscall.EPERM  		return  	}  	// Open directory, safe against symlink races -	pDir := filepath.Dir(pRelPath) +	pDir := filepath.Dir(pPath)  	dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, pDir)  	if err != nil {  		return  	} -	pName = filepath.Base(pRelPath) -	return dirfd, pName, nil +	return dirfd, pPath, nil  } | 
