diff options
| author | Jakob Unterwurzacher | 2020-08-15 17:31:25 +0200 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2020-08-15 17:31:45 +0200 | 
| commit | 94e8fc12ea5756a130e7ac9ed67ddd519b5f3a22 (patch) | |
| tree | 44186807f71db8c555af23f8969f8f1456c955f9 | |
| parent | 15b0b4a5fd268b421ddc347e4417b2538a540922 (diff) | |
v2api/reverse: finish -exclude
Tests pass now.
| -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 | ||||
| -rw-r--r-- | tests/reverse/correctness_test.go | 2 | ||||
| -rw-r--r-- | tests/reverse/exclude_test.go | 2 | ||||
| -rw-r--r-- | tests/reverse/inomap_test.go | 4 | 
10 files changed, 109 insertions, 64 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  } diff --git a/tests/reverse/correctness_test.go b/tests/reverse/correctness_test.go index 47b8b73..6b7ed5f 100644 --- a/tests/reverse/correctness_test.go +++ b/tests/reverse/correctness_test.go @@ -87,7 +87,7 @@ func TestSymlinkDentrySize(t *testing.T) {  	fi, err := os.Lstat(mnt + "/" + symlinkResponse.Result)  	if err != nil { -		t.Errorf("Lstat: %v", err) +		t.Fatalf("Lstat: %v", err)  	}  	target, err := os.Readlink(mnt + "/" + symlinkResponse.Result) diff --git a/tests/reverse/exclude_test.go b/tests/reverse/exclude_test.go index b5d0f5b..c493d95 100644 --- a/tests/reverse/exclude_test.go +++ b/tests/reverse/exclude_test.go @@ -118,6 +118,7 @@ func testExclude(t *testing.T, flag string) {  	cExclude := encryptExcludeTestPaths(t, sock, pExclude)  	// Check that "excluded" paths are not there and "ok" paths are there  	for _, v := range cExclude { +		t.Logf("File %q should be invisible", v)  		if test_helpers.VerifyExistence(t, mnt+"/"+v) {  			t.Errorf("File %q is visible, but should be excluded", v)  		} @@ -126,6 +127,7 @@ func testExclude(t *testing.T, flag string) {  		}  	}  	for _, v := range cOk { +		t.Logf("File %q should be visible", v)  		if !test_helpers.VerifyExistence(t, mnt+"/"+v) {  			t.Errorf("File %q is hidden, but should be visible", v)  		} diff --git a/tests/reverse/inomap_test.go b/tests/reverse/inomap_test.go index 5f7fb45..e6fc525 100644 --- a/tests/reverse/inomap_test.go +++ b/tests/reverse/inomap_test.go @@ -45,7 +45,7 @@ func findIno(dir string, ino uint64) string {  //    ├── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50         <---- child  //    └── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50.name    <---- name  // -// And verifies that the inode numbers match what we expect. +// It verifies that the inode numbers match what we expect.  func TestVirtualFileIno(t *testing.T) {  	if plaintextnames {  		t.Skip("plaintextnames mode does not have virtual files") @@ -111,7 +111,7 @@ func TestVirtualFileIno(t *testing.T) {  		var st2 syscall.Stat_t  		err = syscall.Lstat(encryptedParent+"/"+entry, &st2)  		if err != nil { -			t.Logf("stat %q: %v", entry, err) +			t.Errorf("stat %q: %v", entry, err)  			continue  		}  		if entry == "gocryptfs.diriv" { | 
