fusefrontend: Allow to set/remove xattr on directory without read permission.
Setting/removing extended attributes on directories was partially fixed with commit eff35e60b63331e3e10f921792baa10b236a721d. 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 commit f320b76fd189a363a34bffe981aa67ab97df3362), 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
|
package fusefrontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/hanwen/go-fuse/fuse"
|
"github.com/hanwen/go-fuse/fuse"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
"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
|
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)
|
cAttr := fs.encryptXattrName(attr)
|
||||||
|
|
||||||
cData, err := syscallcompat.Fgetxattr(fd, cAttr)
|
cData, status := fs.getXAttr(relPath, cAttr, context)
|
||||||
if err != nil {
|
if !status.Ok() {
|
||||||
return nil, fuse.ToStatus(err)
|
return nil, status
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := fs.decryptXattrValue(cData)
|
data, err := fs.decryptXattrValue(cData)
|
||||||
@ -69,26 +57,10 @@ func (fs *FS) SetXAttr(relPath string, attr string, data []byte, flags int, cont
|
|||||||
return _EOPNOTSUPP
|
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)
|
flags = filterXattrSetFlags(flags)
|
||||||
cAttr := fs.encryptXattrName(attr)
|
cAttr := fs.encryptXattrName(attr)
|
||||||
cData := fs.encryptXattrValue(data)
|
cData := fs.encryptXattrValue(data)
|
||||||
|
return fs.setXAttr(relPath, cAttr, cData, flags, context)
|
||||||
err = unix.Fsetxattr(fd, cAttr, cData, flags)
|
|
||||||
if err != nil {
|
|
||||||
return fuse.ToStatus(err)
|
|
||||||
}
|
|
||||||
return fuse.OK
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveXAttr - FUSE call.
|
// RemoveXAttr - FUSE call.
|
||||||
@ -102,23 +74,8 @@ func (fs *FS) RemoveXAttr(relPath string, attr string, context *fuse.Context) fu
|
|||||||
return _EOPNOTSUPP
|
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)
|
cAttr := fs.encryptXattrName(attr)
|
||||||
err = unix.Fremovexattr(fd, cAttr)
|
return fs.removeXAttr(relPath, cAttr, context)
|
||||||
if err != nil {
|
|
||||||
return fuse.ToStatus(err)
|
|
||||||
}
|
|
||||||
return fuse.OK
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListXAttr - FUSE call. Lists extended attributes on the file at "relPath".
|
// 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) {
|
if fs.isFiltered(relPath) {
|
||||||
return nil, fuse.EPERM
|
return nil, fuse.EPERM
|
||||||
}
|
}
|
||||||
var cNames []string
|
|
||||||
var err error
|
cNames, status := fs.listXAttr(relPath, context)
|
||||||
if runtime.GOOS == "linux" {
|
if !status.Ok() {
|
||||||
dirfd, cName, err2 := fs.openBackingDir(relPath)
|
return nil, status
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
names := make([]string, 0, len(cNames))
|
names := make([]string, 0, len(cNames))
|
||||||
|
@ -3,6 +3,16 @@
|
|||||||
// Package fusefrontend interfaces directly with the go-fuse library.
|
// Package fusefrontend interfaces directly with the go-fuse library.
|
||||||
package fusefrontend
|
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 {
|
func disallowedXAttrName(attr string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -14,3 +24,71 @@ func filterXattrSetFlags(flags int) int {
|
|||||||
|
|
||||||
return flags &^ XATTR_NOSECURITY
|
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
|
package fusefrontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"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
|
// Only allow the "user" namespace, block "trusted" and "security", as
|
||||||
@ -19,3 +27,57 @@ func disallowedXAttrName(attr string) bool {
|
|||||||
func filterXattrSetFlags(flags int) int {
|
func filterXattrSetFlags(flags int) int {
|
||||||
return flags
|
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
|
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
|
// Flistxattr is a wrapper for unix.Flistxattr that handles buffer sizing and
|
||||||
// parsing the returned blob to a string slice.
|
// parsing the returned blob to a string slice.
|
||||||
func Flistxattr(fd int) (attrs []string, err error) {
|
func Flistxattr(fd int) (attrs []string, err error) {
|
||||||
|
@ -129,7 +129,7 @@ func TestSetGetRmDir(t *testing.T) {
|
|||||||
fn := test_helpers.DefaultPlainDir + "/TestSetGetRmDir"
|
fn := test_helpers.DefaultPlainDir + "/TestSetGetRmDir"
|
||||||
err := syscall.Mkdir(fn, 0700)
|
err := syscall.Mkdir(fn, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("creating fifo failed: %v", err)
|
t.Fatalf("creating directory failed: %v", err)
|
||||||
}
|
}
|
||||||
setGetRmList(fn)
|
setGetRmList(fn)
|
||||||
}
|
}
|
||||||
@ -316,3 +316,31 @@ func TestSet0200File(t *testing.T) {
|
|||||||
t.Error(err)
|
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…
x
Reference in New Issue
Block a user