v2api: implement Getxattr, Setxattr, Removexattr, Listxattr
gocryptfs/tests/xattr passes.
This commit is contained in:
parent
4a0966e79e
commit
57d572dbc1
@ -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))
|
||||
*/
|
||||
|
86
internal/fusefrontend/node_xattr.go
Normal file
86
internal/fusefrontend/node_xattr.go
Normal file
@ -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
|
||||
}
|
0
internal/fusefrontend/node_xattr_darwin.go
Normal file
0
internal/fusefrontend/node_xattr_darwin.go
Normal file
68
internal/fusefrontend/node_xattr_linux.go
Normal file
68
internal/fusefrontend/node_xattr_linux.go
Normal file
@ -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
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user