diff options
| author | Jakob Unterwurzacher | 2020-07-14 19:55:20 +0200 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2020-07-14 19:55:20 +0200 | 
| commit | 57d572dbc10cfb1d14642598b0827d4119b26b64 (patch) | |
| tree | 16e46ff7a7746edfc471ba06ef0570db92fd09fe /internal | |
| parent | 4a0966e79efb157b612e4f5867ec6f111571546c (diff) | |
v2api: implement Getxattr, Setxattr, Removexattr, Listxattr
gocryptfs/tests/xattr passes.
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/fusefrontend/node_api_check.go | 4 | ||||
| -rw-r--r-- | internal/fusefrontend/node_xattr.go | 86 | ||||
| -rw-r--r-- | internal/fusefrontend/node_xattr_darwin.go | 0 | ||||
| -rw-r--r-- | internal/fusefrontend/node_xattr_linux.go | 68 | ||||
| -rw-r--r-- | internal/fusefrontend/openbackingdir_test.go | 2 | ||||
| -rw-r--r-- | internal/fusefrontend/root_node.go | 52 | ||||
| -rw-r--r-- | internal/fusefrontend/xattr.go | 8 | ||||
| -rw-r--r-- | internal/fusefrontend/xattr_linux.go | 4 | ||||
| -rw-r--r-- | internal/fusefrontend/xattr_unit_test.go | 19 | 
9 files changed, 217 insertions, 26 deletions
| diff --git a/internal/fusefrontend/node_api_check.go b/internal/fusefrontend/node_api_check.go index ac48341..0f60c74 100644 --- a/internal/fusefrontend/node_api_check.go +++ b/internal/fusefrontend/node_api_check.go @@ -21,11 +21,11 @@ var _ = (fs.NodeMknoder)((*Node)(nil))  var _ = (fs.NodeLinker)((*Node)(nil))  var _ = (fs.NodeSymlinker)((*Node)(nil))  var _ = (fs.NodeRenamer)((*Node)(nil)) - -/* TODO  var _ = (fs.NodeGetxattrer)((*Node)(nil))  var _ = (fs.NodeSetxattrer)((*Node)(nil))  var _ = (fs.NodeRemovexattrer)((*Node)(nil))  var _ = (fs.NodeListxattrer)((*Node)(nil)) + +/* TODO  var _ = (fs.NodeCopyFileRanger)((*Node)(nil))  */ diff --git a/internal/fusefrontend/node_xattr.go b/internal/fusefrontend/node_xattr.go new file mode 100644 index 0000000..de40915 --- /dev/null +++ b/internal/fusefrontend/node_xattr.go @@ -0,0 +1,86 @@ +// Package fusefrontend interfaces directly with the go-fuse library. +package fusefrontend + +import ( +	"bytes" +	"context" +	"strings" +	"syscall" + +	"github.com/rfjakob/gocryptfs/internal/tlog" +) + +// xattr names are encrypted like file names, but with a fixed IV. +// Padded with "_xx" for length 16. +var xattrNameIV = []byte("xattr_name_iv_xx") + +// We store encrypted xattrs under this prefix plus the base64-encoded +// encrypted original name. +var xattrStorePrefix = "user.gocryptfs." + +// GetXAttr - FUSE call. Reads the value of extended attribute "attr". +// +// This function is symlink-safe through Fgetxattr. +func (n *Node) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) { +	rn := n.rootNode() +	cAttr := rn.encryptXattrName(attr) +	cData, errno := n.getXAttr(cAttr) +	if errno != 0 { +		return 0, errno +	} +	data, err := rn.decryptXattrValue(cData) +	if err != nil { +		tlog.Warn.Printf("GetXAttr: %v", err) +		return ^uint32(0), syscall.EIO +	} +	l := copy(dest, data) +	return uint32(l), 0 +} + +// SetXAttr - FUSE call. Set extended attribute. +// +// This function is symlink-safe through Fsetxattr. +func (n *Node) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno { +	rn := n.rootNode() +	flags = uint32(filterXattrSetFlags(int(flags))) +	cAttr := rn.encryptXattrName(attr) +	cData := rn.encryptXattrValue(data) +	return n.setXAttr(cAttr, cData, flags) +} + +// RemoveXAttr - FUSE call. +// +// This function is symlink-safe through Fremovexattr. +func (n *Node) Removexattr(ctx context.Context, attr string) syscall.Errno { +	rn := n.rootNode() +	cAttr := rn.encryptXattrName(attr) +	return n.removeXAttr(cAttr) +} + +// ListXAttr - FUSE call. Lists extended attributes on the file at "relPath". +// +// This function is symlink-safe through Flistxattr. +func (n *Node) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) { +	cNames, errno := n.listXAttr() +	if errno != 0 { +		return 0, errno +	} +	rn := n.rootNode() +	var buf bytes.Buffer +	for _, curName := range cNames { +		if !strings.HasPrefix(curName, xattrStorePrefix) { +			continue +		} +		name, err := rn.decryptXattrName(curName) +		if err != nil { +			tlog.Warn.Printf("ListXAttr: invalid xattr name %q: %v", curName, err) +			rn.reportMitigatedCorruption(curName) +			continue +		} +		buf.WriteString(name + "\000") +	} +	if buf.Len() > len(dest) { +		return ^uint32(0), syscall.ERANGE +	} +	return uint32(copy(dest, buf.Bytes())), 0 +} diff --git a/internal/fusefrontend/node_xattr_darwin.go b/internal/fusefrontend/node_xattr_darwin.go new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/internal/fusefrontend/node_xattr_darwin.go diff --git a/internal/fusefrontend/node_xattr_linux.go b/internal/fusefrontend/node_xattr_linux.go new file mode 100644 index 0000000..342bdf6 --- /dev/null +++ b/internal/fusefrontend/node_xattr_linux.go @@ -0,0 +1,68 @@ +package fusefrontend + +import ( +	"fmt" +	"syscall" + +	"golang.org/x/sys/unix" + +	"github.com/hanwen/go-fuse/v2/fs" + +	"github.com/rfjakob/gocryptfs/internal/syscallcompat" +) + +func filterXattrSetFlags(flags int) int { +	return flags +} + +func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) { +	dirfd, cName, errno := n.prepareAtSyscall("") +	if errno != 0 { +		return +	} +	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, fs.ToErrno(err) +	} +	return cData, 0 +} + +func (n *Node) setXAttr(cAttr string, cData []byte, flags uint32) (errno syscall.Errno) { +	dirfd, cName, errno := n.prepareAtSyscall("") +	if errno != 0 { +		return +	} +	defer syscall.Close(dirfd) + +	procPath := fmt.Sprintf("/proc/self/fd/%d/%s", dirfd, cName) +	return fs.ToErrno(unix.Lsetxattr(procPath, cAttr, cData, int(flags))) +} + +func (n *Node) removeXAttr(cAttr string) (errno syscall.Errno) { +	dirfd, cName, errno := n.prepareAtSyscall("") +	if errno != 0 { +		return +	} +	defer syscall.Close(dirfd) + +	procPath := fmt.Sprintf("/proc/self/fd/%d/%s", dirfd, cName) +	return fs.ToErrno(unix.Lremovexattr(procPath, cAttr)) +} + +func (n *Node) listXAttr() (out []string, errno syscall.Errno) { +	dirfd, cName, errno := n.prepareAtSyscall("") +	if errno != 0 { +		return +	} +	defer syscall.Close(dirfd) + +	procPath := fmt.Sprintf("/proc/self/fd/%d/%s", dirfd, cName) +	cNames, err := syscallcompat.Llistxattr(procPath) +	if err != nil { +		return nil, fs.ToErrno(err) +	} +	return cNames, 0 +} diff --git a/internal/fusefrontend/openbackingdir_test.go b/internal/fusefrontend/openbackingdir_test.go index 48c20d6..6d799b7 100644 --- a/internal/fusefrontend/openbackingdir_test.go +++ b/internal/fusefrontend/openbackingdir_test.go @@ -40,7 +40,7 @@ func TestOpenBackingDir(t *testing.T) {  	syscall.Close(dirfd)  	// Again, but populate the cache for "" by looking up a non-existing file -	fs.Getattr(nil, "xyz1234", &fuse.AttrOut{}) +	fs.Lookup(nil, "xyz1234", &fuse.EntryOut{})  	dirfd, cName, err = fs.openBackingDir("")  	if err != nil {  		t.Fatal(err) diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go index 72d5581..5870c97 100644 --- a/internal/fusefrontend/root_node.go +++ b/internal/fusefrontend/root_node.go @@ -265,3 +265,55 @@ func (rn *RootNode) encryptSymlinkTarget(data string) (cData64 string) {  	cData64 = rn.nameTransform.B64EncodeToString(cData)  	return cData64  } + +// encryptXattrValue encrypts the xattr value "data". +// The data is encrypted like a file content block, but without binding it to +// a file location (block number and file id are set to zero). +// Special case: an empty value is encrypted to an empty value. +func (rn *RootNode) encryptXattrValue(data []byte) (cData []byte) { +	if len(data) == 0 { +		return []byte{} +	} +	return rn.contentEnc.EncryptBlock(data, 0, nil) +} + +// decryptXattrValue decrypts the xattr value "cData". +func (rn *RootNode) decryptXattrValue(cData []byte) (data []byte, err error) { +	if len(cData) == 0 { +		return []byte{}, nil +	} +	data, err1 := rn.contentEnc.DecryptBlock([]byte(cData), 0, nil) +	if err1 == nil { +		return data, nil +	} +	// This backward compatibility is needed to support old +	// file systems having xattr values base64-encoded. +	cData, err2 := rn.nameTransform.B64DecodeString(string(cData)) +	if err2 != nil { +		// Looks like the value was not base64-encoded, but just corrupt. +		// Return the original decryption error: err1 +		return nil, err1 +	} +	return rn.contentEnc.DecryptBlock([]byte(cData), 0, nil) +} + +// encryptXattrName transforms "user.foo" to "user.gocryptfs.a5sAd4XAa47f5as6dAf" +func (rn *RootNode) encryptXattrName(attr string) (cAttr string) { +	// xattr names are encrypted like file names, but with a fixed IV. +	cAttr = xattrStorePrefix + rn.nameTransform.EncryptName(attr, xattrNameIV) +	return cAttr +} + +func (rn *RootNode) decryptXattrName(cAttr string) (attr string, err error) { +	// Reject anything that does not start with "user.gocryptfs." +	if !strings.HasPrefix(cAttr, xattrStorePrefix) { +		return "", syscall.EINVAL +	} +	// Strip "user.gocryptfs." prefix +	cAttr = cAttr[len(xattrStorePrefix):] +	attr, err = rn.nameTransform.DecryptName(cAttr, xattrNameIV) +	if err != nil { +		return "", err +	} +	return attr, nil +} diff --git a/internal/fusefrontend/xattr.go b/internal/fusefrontend/xattr.go index d55de3e..6638d83 100644 --- a/internal/fusefrontend/xattr.go +++ b/internal/fusefrontend/xattr.go @@ -12,14 +12,6 @@ import (  const _EOPNOTSUPP = fuse.Status(syscall.EOPNOTSUPP) -// xattr names are encrypted like file names, but with a fixed IV. -// Padded with "_xx" for length 16. -var xattrNameIV = []byte("xattr_name_iv_xx") - -// We store encrypted xattrs under this prefix plus the base64-encoded -// encrypted original name. -var xattrStorePrefix = "user.gocryptfs." -  // GetXAttr - FUSE call. Reads the value of extended attribute "attr".  //  // This function is symlink-safe through Fgetxattr. diff --git a/internal/fusefrontend/xattr_linux.go b/internal/fusefrontend/xattr_linux.go index a4d2710..5df0617 100644 --- a/internal/fusefrontend/xattr_linux.go +++ b/internal/fusefrontend/xattr_linux.go @@ -14,10 +14,6 @@ import (  	"github.com/rfjakob/gocryptfs/internal/syscallcompat"  ) -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 { diff --git a/internal/fusefrontend/xattr_unit_test.go b/internal/fusefrontend/xattr_unit_test.go index 0a5e14a..f6c0469 100644 --- a/internal/fusefrontend/xattr_unit_test.go +++ b/internal/fusefrontend/xattr_unit_test.go @@ -31,15 +31,12 @@ func newTestFS(args Args) *RootNode {  }  func TestEncryptDecryptXattrName(t *testing.T) { -	t.Fatal("not yet implemented") -	/* -		fs := newTestFS(Args{}) -		attr1 := "user.foo123456789" -		cAttr := fs.encryptXattrName(attr1) -		t.Logf("cAttr=%v", cAttr) -		attr2, err := fs.decryptXattrName(cAttr) -		if attr1 != attr2 || err != nil { -			t.Fatalf("Decrypt mismatch: %v != %v", attr1, attr2) -		} -	*/ +	fs := newTestFS(Args{}) +	attr1 := "user.foo123456789" +	cAttr := fs.encryptXattrName(attr1) +	t.Logf("cAttr=%v", cAttr) +	attr2, err := fs.decryptXattrName(cAttr) +	if attr1 != attr2 || err != nil { +		t.Fatalf("Decrypt mismatch: %v != %v", attr1, attr2) +	}  } | 
