diff options
-rw-r--r-- | internal/fusefrontend/node.go | 54 | ||||
-rw-r--r-- | internal/fusefrontend/node_openbackingdir.go | 68 | ||||
-rw-r--r-- | internal/syscallcompat/sys_common.go | 12 |
3 files changed, 130 insertions, 4 deletions
diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go index fde6a1a..f246408 100644 --- a/internal/fusefrontend/node.go +++ b/internal/fusefrontend/node.go @@ -2,13 +2,18 @@ package fusefrontend import ( "context" + "path/filepath" "syscall" + "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/inomap" "github.com/rfjakob/gocryptfs/internal/nametransform" + "github.com/rfjakob/gocryptfs/internal/syscallcompat" ) // Node is a file or directory in the filesystem tree @@ -20,25 +25,66 @@ type Node struct { // RootNode is the root of the filesystem tree of Nodes. type RootNode struct { Node - - // This flag is set to zero each time fs.isFiltered() is called + // args stores configuration arguments + args Args + // Filename encryption helper + nameTransform nametransform.NameTransformer + // Content encryption helper + contentEnc *contentenc.ContentEnc + // IsIdle flag is set to zero each time fs.isFiltered() is called // (uint32 so that it can be reset with CompareAndSwapUint32). // When -idle was used when mounting, idleMonitor() sets it to 1 // periodically. IsIdle uint32 + + // inoMap translates inode numbers from different devices to unique inode + // numbers. + inoMap *inomap.InoMap } func NewRootNode(args Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *RootNode { // TODO - return &RootNode{} + return &RootNode{ + args: args, + nameTransform: n, + contentEnc: c, + inoMap: inomap.New(), + } } +// path returns the relative plaintext path of this node func (n *Node) path() string { return n.Path(n.Root()) } +func (n *Node) rootNode() *RootNode { + return n.Root().Operations().(*RootNode) +} + func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { - return nil, 1 + rn := n.rootNode() + p := filepath.Join(n.path(), name) + dirfd, cName, err := rn.openBackingDir(p) + if err != nil { + return nil, fs.ToErrno(err) + } + // Get device number and inode number into `st` + st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW) + if err != nil { + return nil, fs.ToErrno(err) + } + // Get unique inode number + rn.inoMap.TranslateStat(st) + out.Attr.FromStat(st) + // Create child node + id := fs.StableAttr{ + Mode: uint32(st.Mode), + Gen: 1, + Ino: st.Ino, + } + node := &Node{} + ch := n.NewInode(ctx, node, id) + return ch, 0 } func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno { diff --git a/internal/fusefrontend/node_openbackingdir.go b/internal/fusefrontend/node_openbackingdir.go new file mode 100644 index 0000000..733c239 --- /dev/null +++ b/internal/fusefrontend/node_openbackingdir.go @@ -0,0 +1,68 @@ +package fusefrontend + +import ( + "path/filepath" + "strings" + "syscall" + + "github.com/rfjakob/gocryptfs/internal/nametransform" + "github.com/rfjakob/gocryptfs/internal/syscallcompat" +) + +// openBackingDir opens the parent ciphertext directory of plaintext path +// "relPath". It returns the dirfd (opened with O_PATH) and the encrypted +// basename. +// +// The caller should then use Openat(dirfd, cName, ...) and friends. +// For convenience, if relPath is "", cName is going to be ".". +// +// openBackingDir is secure against symlink races by using Openat and +// ReadDirIVAt. +func (rn *RootNode) openBackingDir(relPath string) (dirfd int, cName string, err error) { + dirRelPath := nametransform.Dir(relPath) + // With PlaintextNames, we don't need to read DirIVs. Easy. + if rn.args.PlaintextNames { + dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, dirRelPath) + if err != nil { + return -1, "", err + } + // If relPath is empty, cName is ".". + cName = filepath.Base(relPath) + return dirfd, cName, nil + } + // Open cipherdir (following symlinks) + dirfd, err = syscall.Open(rn.args.Cipherdir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0) + if err != nil { + return -1, "", err + } + // If relPath is empty, cName is ".". + if relPath == "" { + return dirfd, ".", nil + } + // Walk the directory tree + parts := strings.Split(relPath, "/") + for i, name := range parts { + iv, err := nametransform.ReadDirIVAt(dirfd) + if err != nil { + syscall.Close(dirfd) + return -1, "", err + } + cName, err = rn.nameTransform.EncryptAndHashName(name, iv) + if err != nil { + syscall.Close(dirfd) + return -1, "", err + } + // Last part? We are done. + if i == len(parts)-1 { + break + } + // Not the last part? Descend into next directory. + dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0) + syscall.Close(dirfd) + if err != nil { + return -1, "", err + } + dirfd = dirfd2 + } + return dirfd, cName, nil +} diff --git a/internal/syscallcompat/sys_common.go b/internal/syscallcompat/sys_common.go index b6bbdff..d5e3251 100644 --- a/internal/syscallcompat/sys_common.go +++ b/internal/syscallcompat/sys_common.go @@ -107,6 +107,18 @@ func Fstatat(dirfd int, path string, stat *unix.Stat_t, flags int) (err error) { return unix.Fstatat(dirfd, path, stat, flags) } +// Fstatat2 is a more convenient version of Fstatat. It allocates a Stat_t +// for you and also handles the Unix2syscall conversion. +func Fstatat2(dirfd int, path string, flags int) (*syscall.Stat_t, error) { + var stUnix unix.Stat_t + err := Fstatat(dirfd, path, &stUnix, flags) + if err != nil { + return nil, err + } + st := Unix2syscall(stUnix) + return &st, nil +} + const XATTR_SIZE_MAX = 65536 // Make the buffer 1kB bigger so we can detect overflows |