diff options
| -rw-r--r-- | internal/fusefrontend/xattr.go | 83 | ||||
| -rw-r--r-- | internal/fusefrontend/xattr_darwin.go | 78 | ||||
| -rw-r--r-- | internal/fusefrontend/xattr_linux.go | 62 | ||||
| -rw-r--r-- | internal/syscallcompat/sys_common.go | 23 | ||||
| -rw-r--r-- | tests/xattr/xattr_integration_test.go | 30 | 
5 files changed, 201 insertions, 75 deletions
| diff --git a/internal/fusefrontend/xattr.go b/internal/fusefrontend/xattr.go index 2248bad..1de9cac 100644 --- a/internal/fusefrontend/xattr.go +++ b/internal/fusefrontend/xattr.go @@ -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)) diff --git a/internal/fusefrontend/xattr_darwin.go b/internal/fusefrontend/xattr_darwin.go index 8760480..741eb6c 100644 --- a/internal/fusefrontend/xattr_darwin.go +++ b/internal/fusefrontend/xattr_darwin.go @@ -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 +} diff --git a/internal/fusefrontend/xattr_linux.go b/internal/fusefrontend/xattr_linux.go index fd3b7e5..3a64412 100644 --- a/internal/fusefrontend/xattr_linux.go +++ b/internal/fusefrontend/xattr_linux.go @@ -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 +} diff --git a/internal/syscallcompat/sys_common.go b/internal/syscallcompat/sys_common.go index 4ce0208..a1aa66d 100644 --- a/internal/syscallcompat/sys_common.go +++ b/internal/syscallcompat/sys_common.go @@ -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) { diff --git a/tests/xattr/xattr_integration_test.go b/tests/xattr/xattr_integration_test.go index f182fd9..29c3e84 100644 --- a/tests/xattr/xattr_integration_test.go +++ b/tests/xattr/xattr_integration_test.go @@ -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) +	} +} | 
