fusefrontend: Allow to set/remove xattr on directory without read permission.
Setting/removing extended attributes on directories was partially fixed with commiteff35e60b6
. However, on most file systems it is also possible to do these operations without read access (see tests). Since we cannot open a write-access fd to a directory, we have to use the /proc/self/fd trick (already used for ListXAttr) for the other operations aswell. For simplicity, let's separate the Linux and Darwin code again (basically revert commitf320b76fd1
), and always use the /proc/self/fd trick on Linux. On Darwin we use the best-effort approach with openBackingFile() as a fallback. More discussion about the available options is available in https://github.com/rfjakob/gocryptfs/issues/308.
This commit is contained in:
parent
f17721c364
commit
5055f39bd5
|
@ -2,16 +2,11 @@
|
|||
package fusefrontend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/hanwen/go-fuse/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
|
@ -36,18 +31,11 @@ func (fs *FS) GetXAttr(relPath string, attr string, context *fuse.Context) ([]by
|
|||
return nil, _EOPNOTSUPP
|
||||
}
|
||||
|
||||
// O_NONBLOCK to not block on FIFOs.
|
||||
fd, err := fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_NONBLOCK)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
cAttr := fs.encryptXattrName(attr)
|
||||
|
||||
cData, err := syscallcompat.Fgetxattr(fd, cAttr)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
cData, status := fs.getXAttr(relPath, cAttr, context)
|
||||
if !status.Ok() {
|
||||
return nil, status
|
||||
}
|
||||
|
||||
data, err := fs.decryptXattrValue(cData)
|
||||
|
@ -69,26 +57,10 @@ func (fs *FS) SetXAttr(relPath string, attr string, data []byte, flags int, cont
|
|||
return _EOPNOTSUPP
|
||||
}
|
||||
|
||||
// O_NONBLOCK to not block on FIFOs.
|
||||
fd, err := fs.openBackingFile(relPath, syscall.O_WRONLY|syscall.O_NONBLOCK)
|
||||
// Directories cannot be opened read-write. Retry.
|
||||
if err == syscall.EISDIR {
|
||||
fd, err = fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK)
|
||||
}
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
flags = filterXattrSetFlags(flags)
|
||||
cAttr := fs.encryptXattrName(attr)
|
||||
cData := fs.encryptXattrValue(data)
|
||||
|
||||
err = unix.Fsetxattr(fd, cAttr, cData, flags)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
return fuse.OK
|
||||
return fs.setXAttr(relPath, cAttr, cData, flags, context)
|
||||
}
|
||||
|
||||
// RemoveXAttr - FUSE call.
|
||||
|
@ -102,23 +74,8 @@ func (fs *FS) RemoveXAttr(relPath string, attr string, context *fuse.Context) fu
|
|||
return _EOPNOTSUPP
|
||||
}
|
||||
|
||||
// O_NONBLOCK to not block on FIFOs.
|
||||
fd, err := fs.openBackingFile(relPath, syscall.O_WRONLY|syscall.O_NONBLOCK)
|
||||
// Directories cannot be opened read-write. Retry.
|
||||
if err == syscall.EISDIR {
|
||||
fd, err = fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK)
|
||||
}
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
cAttr := fs.encryptXattrName(attr)
|
||||
err = unix.Fremovexattr(fd, cAttr)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
return fuse.OK
|
||||
return fs.removeXAttr(relPath, cAttr, context)
|
||||
}
|
||||
|
||||
// ListXAttr - FUSE call. Lists extended attributes on the file at "relPath".
|
||||
|
@ -128,32 +85,10 @@ func (fs *FS) ListXAttr(relPath string, context *fuse.Context) ([]string, fuse.S
|
|||
if fs.isFiltered(relPath) {
|
||||
return nil, fuse.EPERM
|
||||
}
|
||||
var cNames []string
|
||||
var err error
|
||||
if runtime.GOOS == "linux" {
|
||||
dirfd, cName, err2 := fs.openBackingDir(relPath)
|
||||
if err2 != nil {
|
||||
return nil, fuse.ToStatus(err2)
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
procPath := fmt.Sprintf("/proc/self/fd/%d/%s", dirfd, cName)
|
||||
cNames, err = syscallcompat.Llistxattr(procPath)
|
||||
} else {
|
||||
// O_NONBLOCK to not block on FIFOs.
|
||||
fd, err2 := fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_NONBLOCK)
|
||||
// On a symlink, openBackingFile fails with ELOOP. Let's pretend there
|
||||
// can be no xattrs on symlinks, and always return an empty result.
|
||||
if err2 == syscall.ELOOP {
|
||||
return nil, fuse.OK
|
||||
}
|
||||
if err2 != nil {
|
||||
return nil, fuse.ToStatus(err2)
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
cNames, err = syscallcompat.Flistxattr(fd)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
|
||||
cNames, status := fs.listXAttr(relPath, context)
|
||||
if !status.Ok() {
|
||||
return nil, status
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(cNames))
|
||||
|
|
|
@ -3,6 +3,16 @@
|
|||
// Package fusefrontend interfaces directly with the go-fuse library.
|
||||
package fusefrontend
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/hanwen/go-fuse/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||
)
|
||||
|
||||
func disallowedXAttrName(attr string) bool {
|
||||
return false
|
||||
}
|
||||
|
@ -14,3 +24,71 @@ func filterXattrSetFlags(flags int) int {
|
|||
|
||||
return flags &^ XATTR_NOSECURITY
|
||||
}
|
||||
|
||||
func (fs *FS) getXAttr(relPath string, cAttr string, context *fuse.Context) ([]byte, fuse.Status) {
|
||||
// O_NONBLOCK to not block on FIFOs.
|
||||
fd, err := fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_NONBLOCK)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
cData, err := syscallcompat.Fgetxattr(fd, cAttr)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
return cData, fuse.OK
|
||||
}
|
||||
|
||||
func (fs *FS) setXAttr(relPath string, cAttr string, cData []byte, flags int, context *fuse.Context) fuse.Status {
|
||||
// O_NONBLOCK to not block on FIFOs.
|
||||
fd, err := fs.openBackingFile(relPath, syscall.O_WRONLY|syscall.O_NONBLOCK)
|
||||
// Directories cannot be opened read-write. Retry.
|
||||
if err == syscall.EISDIR {
|
||||
fd, err = fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK)
|
||||
}
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
err = unix.Fsetxattr(fd, cAttr, cData, flags)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
func (fs *FS) removeXAttr(relPath string, cAttr string, context *fuse.Context) fuse.Status {
|
||||
// O_NONBLOCK to not block on FIFOs.
|
||||
fd, err := fs.openBackingFile(relPath, syscall.O_WRONLY|syscall.O_NONBLOCK)
|
||||
// Directories cannot be opened read-write. Retry.
|
||||
if err == syscall.EISDIR {
|
||||
fd, err = fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK)
|
||||
}
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
err = unix.Fremovexattr(fd, cAttr)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
func (fs *FS) listXAttr(relPath string, context *fuse.Context) ([]string, fuse.Status) {
|
||||
// O_NONBLOCK to not block on FIFOs.
|
||||
fd, err := fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_NONBLOCK)
|
||||
// On a symlink, openBackingFile fails with ELOOP. Let's pretend there
|
||||
// can be no xattrs on symlinks, and always return an empty result.
|
||||
if err == syscall.ELOOP {
|
||||
return nil, fuse.OK
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
cNames, err := syscallcompat.Flistxattr(fd)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
return cNames, fuse.OK
|
||||
}
|
||||
|
|
|
@ -4,7 +4,15 @@
|
|||
package fusefrontend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/hanwen/go-fuse/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||
)
|
||||
|
||||
// Only allow the "user" namespace, block "trusted" and "security", as
|
||||
|
@ -19,3 +27,57 @@ func disallowedXAttrName(attr string) bool {
|
|||
func filterXattrSetFlags(flags int) int {
|
||||
return flags
|
||||
}
|
||||
|
||||
func (fs *FS) getXAttr(relPath string, cAttr string, context *fuse.Context) ([]byte, fuse.Status) {
|
||||
dirfd, cName, err := fs.openBackingDir(relPath)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
|
||||
procPath := fmt.Sprintf("/proc/self/fd/%d/%s", dirfd, cName)
|
||||
cData, err := syscallcompat.Lgetxattr(procPath, cAttr)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
return cData, fuse.OK
|
||||
}
|
||||
|
||||
func (fs *FS) setXAttr(relPath string, cAttr string, cData []byte, flags int, context *fuse.Context) fuse.Status {
|
||||
dirfd, cName, err := fs.openBackingDir(relPath)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
|
||||
procPath := fmt.Sprintf("/proc/self/fd/%d/%s", dirfd, cName)
|
||||
err = unix.Lsetxattr(procPath, cAttr, cData, flags)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
func (fs *FS) removeXAttr(relPath string, cAttr string, context *fuse.Context) fuse.Status {
|
||||
dirfd, cName, err := fs.openBackingDir(relPath)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
|
||||
procPath := fmt.Sprintf("/proc/self/fd/%d/%s", dirfd, cName)
|
||||
err = unix.Lremovexattr(procPath, cAttr)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
func (fs *FS) listXAttr(relPath string, context *fuse.Context) ([]string, fuse.Status) {
|
||||
dirfd, cName, err := fs.openBackingDir(relPath)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
|
||||
procPath := fmt.Sprintf("/proc/self/fd/%d/%s", dirfd, cName)
|
||||
cNames, err := syscallcompat.Llistxattr(procPath)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
return cNames, fuse.OK
|
||||
}
|
||||
|
|
|
@ -84,6 +84,29 @@ func Fgetxattr(fd int, attr string) (val []byte, err error) {
|
|||
return val, nil
|
||||
}
|
||||
|
||||
// Lgetxattr is a wrapper around unix.Lgetxattr that handles the buffer sizing.
|
||||
func Lgetxattr(path string, attr string) (val []byte, err error) {
|
||||
// See the buffer sizing comments in Fgetxattr.
|
||||
// TODO: smarter buffer sizing?
|
||||
buf := make([]byte, XATTR_BUFSZ)
|
||||
sz, err := unix.Lgetxattr(path, attr, buf)
|
||||
if err == syscall.ERANGE {
|
||||
// Do NOT return ERANGE - the user might retry ad inifinitum!
|
||||
return nil, syscall.EOVERFLOW
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sz >= XATTR_SIZE_MAX {
|
||||
return nil, syscall.EOVERFLOW
|
||||
}
|
||||
// Copy only the actually used bytes to a new (smaller) buffer
|
||||
// so "buf" never leaves the function and can be allocated on the stack.
|
||||
val = make([]byte, sz)
|
||||
copy(val, buf)
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// Flistxattr is a wrapper for unix.Flistxattr that handles buffer sizing and
|
||||
// parsing the returned blob to a string slice.
|
||||
func Flistxattr(fd int) (attrs []string, err error) {
|
||||
|
|
|
@ -129,7 +129,7 @@ func TestSetGetRmDir(t *testing.T) {
|
|||
fn := test_helpers.DefaultPlainDir + "/TestSetGetRmDir"
|
||||
err := syscall.Mkdir(fn, 0700)
|
||||
if err != nil {
|
||||
t.Fatalf("creating fifo failed: %v", err)
|
||||
t.Fatalf("creating directory failed: %v", err)
|
||||
}
|
||||
setGetRmList(fn)
|
||||
}
|
||||
|
@ -316,3 +316,31 @@ func TestSet0200File(t *testing.T) {
|
|||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Listing xattrs should work even when we don't have read access
|
||||
func TestList0000Dir(t *testing.T) {
|
||||
fn := test_helpers.DefaultPlainDir + "/TestList0000Dir"
|
||||
err := syscall.Mkdir(fn, 0000)
|
||||
if err != nil {
|
||||
t.Fatalf("creating directory failed: %v", err)
|
||||
}
|
||||
_, err = xattr.LList(fn)
|
||||
os.Chmod(fn, 0700)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Setting xattrs should work even when we don't have read access
|
||||
func TestSet0200Dir(t *testing.T) {
|
||||
fn := test_helpers.DefaultPlainDir + "/TestSet0200Dir"
|
||||
err := syscall.Mkdir(fn, 0200)
|
||||
if err != nil {
|
||||
t.Fatalf("creating directory failed: %v", err)
|
||||
}
|
||||
err = xattr.LSet(fn, "user.foo", []byte("bar"))
|
||||
os.Chmod(fn, 0700)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue