summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/fusefrontend_reverse/excluder.go6
-rw-r--r--internal/fusefrontend_reverse/excluder_test.go8
-rw-r--r--internal/fusefrontend_reverse/node.go33
-rw-r--r--internal/fusefrontend_reverse/node_dir_ops.go17
-rw-r--r--internal/fusefrontend_reverse/node_helpers.go51
-rw-r--r--internal/fusefrontend_reverse/root_node.go37
-rw-r--r--internal/fusefrontend_reverse/rpath.go13
-rw-r--r--tests/reverse/correctness_test.go2
-rw-r--r--tests/reverse/exclude_test.go2
-rw-r--r--tests/reverse/inomap_test.go4
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" {