aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2020-07-14 19:55:20 +0200
committerJakob Unterwurzacher2020-07-14 19:55:20 +0200
commit57d572dbc10cfb1d14642598b0827d4119b26b64 (patch)
tree16e46ff7a7746edfc471ba06ef0570db92fd09fe
parent4a0966e79efb157b612e4f5867ec6f111571546c (diff)
v2api: implement Getxattr, Setxattr, Removexattr, Listxattr
gocryptfs/tests/xattr passes.
-rw-r--r--internal/fusefrontend/node_api_check.go4
-rw-r--r--internal/fusefrontend/node_xattr.go86
-rw-r--r--internal/fusefrontend/node_xattr_darwin.go0
-rw-r--r--internal/fusefrontend/node_xattr_linux.go68
-rw-r--r--internal/fusefrontend/openbackingdir_test.go2
-rw-r--r--internal/fusefrontend/root_node.go52
-rw-r--r--internal/fusefrontend/xattr.go8
-rw-r--r--internal/fusefrontend/xattr_linux.go4
-rw-r--r--internal/fusefrontend/xattr_unit_test.go19
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)
+ }
}