v2api: implement Getxattr, Setxattr, Removexattr, Listxattr

gocryptfs/tests/xattr passes.
This commit is contained in:
Jakob Unterwurzacher 2020-07-14 19:55:20 +02:00
parent 4a0966e79e
commit 57d572dbc1
9 changed files with 217 additions and 26 deletions

View File

@ -21,11 +21,11 @@ var _ = (fs.NodeMknoder)((*Node)(nil))
var _ = (fs.NodeLinker)((*Node)(nil)) var _ = (fs.NodeLinker)((*Node)(nil))
var _ = (fs.NodeSymlinker)((*Node)(nil)) var _ = (fs.NodeSymlinker)((*Node)(nil))
var _ = (fs.NodeRenamer)((*Node)(nil)) var _ = (fs.NodeRenamer)((*Node)(nil))
/* TODO
var _ = (fs.NodeGetxattrer)((*Node)(nil)) var _ = (fs.NodeGetxattrer)((*Node)(nil))
var _ = (fs.NodeSetxattrer)((*Node)(nil)) var _ = (fs.NodeSetxattrer)((*Node)(nil))
var _ = (fs.NodeRemovexattrer)((*Node)(nil)) var _ = (fs.NodeRemovexattrer)((*Node)(nil))
var _ = (fs.NodeListxattrer)((*Node)(nil)) var _ = (fs.NodeListxattrer)((*Node)(nil))
/* TODO
var _ = (fs.NodeCopyFileRanger)((*Node)(nil)) var _ = (fs.NodeCopyFileRanger)((*Node)(nil))
*/ */

View 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
}

View 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
}

View File

@ -40,7 +40,7 @@ func TestOpenBackingDir(t *testing.T) {
syscall.Close(dirfd) syscall.Close(dirfd)
// Again, but populate the cache for "" by looking up a non-existing file // 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("") dirfd, cName, err = fs.openBackingDir("")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -265,3 +265,55 @@ func (rn *RootNode) encryptSymlinkTarget(data string) (cData64 string) {
cData64 = rn.nameTransform.B64EncodeToString(cData) cData64 = rn.nameTransform.B64EncodeToString(cData)
return cData64 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
}

View File

@ -12,14 +12,6 @@ import (
const _EOPNOTSUPP = fuse.Status(syscall.EOPNOTSUPP) 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". // GetXAttr - FUSE call. Reads the value of extended attribute "attr".
// //
// This function is symlink-safe through Fgetxattr. // This function is symlink-safe through Fgetxattr.

View File

@ -14,10 +14,6 @@ import (
"github.com/rfjakob/gocryptfs/internal/syscallcompat" "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) { func (fs *FS) getXAttr(relPath string, cAttr string, context *fuse.Context) ([]byte, fuse.Status) {
dirfd, cName, err := fs.openBackingDir(relPath) dirfd, cName, err := fs.openBackingDir(relPath)
if err != nil { if err != nil {

View File

@ -31,15 +31,12 @@ func newTestFS(args Args) *RootNode {
} }
func TestEncryptDecryptXattrName(t *testing.T) { func TestEncryptDecryptXattrName(t *testing.T) {
t.Fatal("not yet implemented") fs := newTestFS(Args{})
/* attr1 := "user.foo123456789"
fs := newTestFS(Args{}) cAttr := fs.encryptXattrName(attr1)
attr1 := "user.foo123456789" t.Logf("cAttr=%v", cAttr)
cAttr := fs.encryptXattrName(attr1) attr2, err := fs.decryptXattrName(cAttr)
t.Logf("cAttr=%v", cAttr) if attr1 != attr2 || err != nil {
attr2, err := fs.decryptXattrName(cAttr) t.Fatalf("Decrypt mismatch: %v != %v", attr1, attr2)
if attr1 != attr2 || err != nil { }
t.Fatalf("Decrypt mismatch: %v != %v", attr1, attr2)
}
*/
} }