From 192a29075a7a567931959c2b4c8e4a9513742eee Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sun, 21 Jun 2020 13:46:08 +0200 Subject: [PATCH] v2api: implement Mkdir --- internal/fusefrontend/fs_dir.go | 4 +- internal/fusefrontend/node_api_check.go | 11 +++ internal/fusefrontend/node_dir_ops.go | 121 ++++++++++++++++++++++++ internal/fusefrontend/root_node.go | 5 + internal/syscallcompat/sys_linux.go | 10 +- 5 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 internal/fusefrontend/node_api_check.go diff --git a/internal/fusefrontend/fs_dir.go b/internal/fusefrontend/fs_dir.go index 8481d56..0d8adae 100644 --- a/internal/fusefrontend/fs_dir.go +++ b/internal/fusefrontend/fs_dir.go @@ -30,7 +30,7 @@ func (fs *FS) mkdirWithIv(dirfd int, cName string, mode uint32, context *fuse.Co // from seeing it. fs.dirIVLock.Lock() defer fs.dirIVLock.Unlock() - err := syscallcompat.MkdiratUser(dirfd, cName, mode, context) + err := syscallcompat.MkdiratUser(dirfd, cName, mode, &context.Caller) if err != nil { return err } @@ -67,7 +67,7 @@ func (fs *FS) Mkdir(newPath string, mode uint32, context *fuse.Context) (code fu context = nil } if fs.args.PlaintextNames { - err = syscallcompat.MkdiratUser(dirfd, cName, mode, context) + err = syscallcompat.MkdiratUser(dirfd, cName, mode, &context.Caller) return fuse.ToStatus(err) } diff --git a/internal/fusefrontend/node_api_check.go b/internal/fusefrontend/node_api_check.go new file mode 100644 index 0000000..e6d1681 --- /dev/null +++ b/internal/fusefrontend/node_api_check.go @@ -0,0 +1,11 @@ +package fusefrontend + +import ( + "github.com/hanwen/go-fuse/v2/fs" +) + +var _ = (fs.NodeGetattrer)((*Node)(nil)) +var _ = (fs.NodeLookuper)((*Node)(nil)) +var _ = (fs.NodeReaddirer)((*Node)(nil)) +var _ = (fs.NodeCreater)((*Node)(nil)) +var _ = (fs.NodeMkdirer)((*Node)(nil)) diff --git a/internal/fusefrontend/node_dir_ops.go b/internal/fusefrontend/node_dir_ops.go index 38d0940..0789c92 100644 --- a/internal/fusefrontend/node_dir_ops.go +++ b/internal/fusefrontend/node_dir_ops.go @@ -5,6 +5,8 @@ import ( "path/filepath" "syscall" + "golang.org/x/sys/unix" + "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" @@ -14,6 +16,125 @@ import ( "github.com/rfjakob/gocryptfs/internal/tlog" ) +// mkdirWithIv - create a new directory and corresponding diriv file. dirfd +// should be a handle to the parent directory, cName is the name of the new +// directory and mode specifies the access permissions to use. +func (n *Node) mkdirWithIv(dirfd int, cName string, mode uint32, caller *fuse.Caller) error { + rn := n.rootNode() + // Between the creation of the directory and the creation of gocryptfs.diriv + // the directory is inconsistent. Take the lock to prevent other readers + // from seeing it. + rn.dirIVLock.Lock() + defer rn.dirIVLock.Unlock() + err := syscallcompat.MkdiratUser(dirfd, cName, mode, caller) + if err != nil { + return err + } + dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_DIRECTORY|syscall.O_NOFOLLOW|syscallcompat.O_PATH, 0) + if err == nil { + // Create gocryptfs.diriv + err = nametransform.WriteDirIVAt(dirfd2) + syscall.Close(dirfd2) + } + if err != nil { + // Delete inconsistent directory (missing gocryptfs.diriv!) + err2 := syscallcompat.Unlinkat(dirfd, cName, unix.AT_REMOVEDIR) + if err2 != nil { + tlog.Warn.Printf("mkdirWithIv: rollback failed: %v", err2) + } + } + return err +} + +// Mkdir - FUSE call. Create a directory at "newPath" with permissions "mode". +// +// Symlink-safe through use of Mkdirat(). +func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { + rn := n.rootNode() + newPath := filepath.Join(n.path(), name) + if rn.isFiltered(newPath) { + return nil, syscall.EPERM + } + dirfd, cName, err := rn.openBackingDir(newPath) + if err != nil { + return nil, fs.ToErrno(err) + } + defer syscall.Close(dirfd) + var caller *fuse.Caller + if rn.args.PreserveOwner { + caller, _ = fuse.FromContext(ctx) + } + if rn.args.PlaintextNames { + err = syscallcompat.MkdiratUser(dirfd, cName, mode, caller) + return nil, fs.ToErrno(err) + } + + // We need write and execute permissions to create gocryptfs.diriv. + // Also, we need read permissions to open the directory (to avoid + // race-conditions between getting and setting the mode). + origMode := mode + mode = mode | 0700 + + // Handle long file name + if nametransform.IsLongContent(cName) { + // Create ".name" + err = rn.nameTransform.WriteLongNameAt(dirfd, cName, newPath) + if err != nil { + return nil, fs.ToErrno(err) + } + + // Create directory + err = rn.mkdirWithIv(dirfd, cName, mode, caller) + if err != nil { + nametransform.DeleteLongNameAt(dirfd, cName) + return nil, fs.ToErrno(err) + } + } else { + err = rn.mkdirWithIv(dirfd, cName, mode, caller) + if err != nil { + return nil, fs.ToErrno(err) + } + } + + fd, err := syscallcompat.Openat(dirfd, cName, + syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) + if err != nil { + tlog.Warn.Printf("Mkdir %q: Openat failed: %v", cName, err) + return nil, fs.ToErrno(err) + } + defer syscall.Close(fd) + + // Get unique inode number + var st syscall.Stat_t + err = syscall.Fstat(fd, &st) + if err != nil { + tlog.Warn.Printf("Mkdir %q: Fstat failed: %v", cName, err) + return nil, fs.ToErrno(err) + } + 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) + + // Set mode + if origMode != mode { + // Preserve SGID bit if it was set due to inheritance. + origMode = uint32(st.Mode&^0777) | origMode + err = syscall.Fchmod(fd, origMode) + if err != nil { + tlog.Warn.Printf("Mkdir %q: Fchmod %#o -> %#o failed: %v", cName, mode, origMode, err) + } + } + + return ch, 0 +} + func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { rn := n.rootNode() p := n.path() diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go index c84ac93..be82851 100644 --- a/internal/fusefrontend/root_node.go +++ b/internal/fusefrontend/root_node.go @@ -2,6 +2,7 @@ package fusefrontend import ( "os" + "sync" "sync/atomic" "syscall" "time" @@ -19,6 +20,10 @@ type RootNode struct { Node // args stores configuration arguments args Args + // dirIVLock: Lock()ed if any "gocryptfs.diriv" file is modified + // Readers must RLock() it to prevent them from seeing intermediate + // states + dirIVLock sync.RWMutex // Filename encryption helper nameTransform nametransform.NameTransformer // Content encryption helper diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go index 02064ac..c82480e 100644 --- a/internal/syscallcompat/sys_linux.go +++ b/internal/syscallcompat/sys_linux.go @@ -233,24 +233,24 @@ func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.C } // MkdiratUser runs the Mkdirat syscall in the context of a different user. -func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) { - if context != nil { +func MkdiratUser(dirfd int, path string, mode uint32, caller *fuse.Caller) (err error) { + if caller != nil { runtime.LockOSThread() defer runtime.UnlockOSThread() - err = syscall.Setgroups(getSupplementaryGroups(context.Pid)) + err = syscall.Setgroups(getSupplementaryGroups(caller.Pid)) if err != nil { return err } defer syscall.Setgroups(nil) - err = syscall.Setregid(-1, int(context.Owner.Gid)) + err = syscall.Setregid(-1, int(caller.Gid)) if err != nil { return err } defer syscall.Setregid(-1, 0) - err = syscall.Setreuid(-1, int(context.Owner.Uid)) + err = syscall.Setreuid(-1, int(caller.Uid)) if err != nil { return err }