diff options
Diffstat (limited to 'internal')
-rw-r--r-- | internal/contentenc/content.go | 5 | ||||
-rw-r--r-- | internal/fusefrontend/fs.go | 20 | ||||
-rw-r--r-- | internal/fusefrontend/xattr.go | 157 | ||||
-rw-r--r-- | internal/fusefrontend/xattr_unit_test.go | 41 |
4 files changed, 201 insertions, 22 deletions
diff --git a/internal/contentenc/content.go b/internal/contentenc/content.go index e2a531c..c4ba7c9 100644 --- a/internal/contentenc/content.go +++ b/internal/contentenc/content.go @@ -131,10 +131,11 @@ func (be *ContentEnc) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, file // concatAD concatenates the block number and the file ID to a byte blob // that can be passed to AES-GCM as associated data (AD). -// Result is: aData = blockNo.bigEndian + fileID. +// Result is: aData = [blockNo.bigEndian fileID]. func concatAD(blockNo uint64, fileID []byte) (aData []byte) { if fileID != nil && len(fileID) != headerIDLen { - // fileID is nil when decrypting the master key from the config file + // fileID is nil when decrypting the master key from the config file, + // and for symlinks and xattrs. log.Panicf("wrong fileID length: %d", len(fileID)) } const lenUint64 = 8 diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index 00361e8..738f113 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -592,23 +592,3 @@ func (fs *FS) Access(path string, mode uint32, context *fuse.Context) (code fuse } return fuse.ToStatus(syscall.Access(cPath, mode)) } - -// GetXAttr implements pathfs.Filesystem. -func (fs *FS) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) { - return nil, fuse.ENOSYS -} - -// SetXAttr implements pathfs.Filesystem. -func (fs *FS) SetXAttr(name string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status { - return fuse.ENOSYS -} - -// ListXAttr implements pathfs.Filesystem. -func (fs *FS) ListXAttr(name string, context *fuse.Context) ([]string, fuse.Status) { - return nil, fuse.ENOSYS -} - -// RemoveXAttr implements pathfs.Filesystem. -func (fs *FS) RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status { - return fuse.ENOSYS -} diff --git a/internal/fusefrontend/xattr.go b/internal/fusefrontend/xattr.go new file mode 100644 index 0000000..357e889 --- /dev/null +++ b/internal/fusefrontend/xattr.go @@ -0,0 +1,157 @@ +// Package fusefrontend interfaces directly with the go-fuse library. +package fusefrontend + +// FUSE operations on paths + +import ( + "strings" + "syscall" + + "github.com/hanwen/go-fuse/fuse" + xattr "github.com/rfjakob/pkg-xattr" + + "github.com/rfjakob/gocryptfs/internal/tlog" +) + +// xattr names are encrypted like file names, but with a fixed IV. +var xattrNameIV = []byte("xattr_name_iv_xx") + +// Only allow the "user" namespace, block "trusted" and "security", as +// these may be interpreted by the system, and we don't want to cause +// trouble with our encrypted garbage. +var xattrUserPrefix = "user." + +// We store encrypted xattrs under this prefix plus the base64-encoded +// encrypted original name. +var xattrStorePrefix = "user.gocryptfs." + +// GetXAttr: read the value of extended attribute "attr". +// Implements pathfs.Filesystem. +func (fs *FS) GetXAttr(path string, attr string, context *fuse.Context) ([]byte, fuse.Status) { + if fs.isFiltered(path) { + return nil, fuse.EPERM + } + cAttr, err := fs.encryptXattrName(attr) + if err != nil { + return nil, fuse.ToStatus(err) + } + cPath, err := fs.getBackingPath(path) + if err != nil { + return nil, fuse.ToStatus(err) + } + cData64, err := xattr.Get(cPath, cAttr) + if err != nil { + return nil, unpackXattrErr(err) + } + // xattr data is decrypted like a symlink target + data, err := fs.decryptSymlinkTarget(string(cData64)) + if err != nil { + tlog.Warn.Printf("GetXAttr: %v", err) + return nil, fuse.EIO + } + return []byte(data), fuse.OK +} + +// SetXAttr implements pathfs.Filesystem. +func (fs *FS) SetXAttr(path string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status { + if fs.isFiltered(path) { + return fuse.EPERM + } + if flags != 0 { + return fuse.EPERM + } + cPath, err := fs.getBackingPath(path) + if err != nil { + return fuse.ToStatus(err) + } + cAttr, err := fs.encryptXattrName(attr) + if err != nil { + return fuse.ToStatus(err) + } + // xattr data is encrypted like a symlink target + cData64 := []byte(fs.encryptSymlinkTarget(string(data))) + return unpackXattrErr(xattr.Set(cPath, cAttr, cData64)) +} + +// RemoveXAttr implements pathfs.Filesystem. +func (fs *FS) RemoveXAttr(path string, attr string, context *fuse.Context) fuse.Status { + if fs.isFiltered(path) { + return fuse.EPERM + } + cPath, err := fs.getBackingPath(path) + if err != nil { + return fuse.ToStatus(err) + } + cAttr, err := fs.encryptXattrName(attr) + if err != nil { + return fuse.ToStatus(err) + } + return unpackXattrErr(xattr.Remove(cPath, cAttr)) +} + +// ListXAttr implements pathfs.Filesystem. +func (fs *FS) ListXAttr(path string, context *fuse.Context) ([]string, fuse.Status) { + if fs.isFiltered(path) { + return nil, fuse.EPERM + } + cPath, err := fs.getBackingPath(path) + if err != nil { + return nil, fuse.ToStatus(err) + } + cNames, err := xattr.List(cPath) + if err != nil { + return nil, unpackXattrErr(err) + } + names := make([]string, 0, len(cNames)) + for _, curName := range cNames { + if !strings.HasPrefix(curName, xattrStorePrefix) { + continue + } + name, err := fs.decryptXattrName(curName) + if err != nil { + tlog.Warn.Printf("ListXAttr: invalid xattr name %q: %v", curName, err) + continue + } + names = append(names, name) + } + return names, fuse.OK +} + +// encryptXattrName transforms "user.foo" to "user.gocryptfs.a5sAd4XAa47f5as6dAf" +func (fs *FS) encryptXattrName(attr string) (cAttr string, err error) { + // Reject anything that does not start with "user." + if !strings.HasPrefix(attr, xattrUserPrefix) { + return "", syscall.EPERM + } + // xattr names are encrypted like file names, but with a fixed IV. + cAttr = xattrStorePrefix + fs.nameTransform.EncryptName(attr, xattrNameIV) + return cAttr, nil +} + +func (fs *FS) 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 = fs.nameTransform.DecryptName(cAttr, xattrNameIV) + if err != nil { + return "", err + } + return attr, nil +} + +// unpackXattrErr unpacks an error value that we got from xattr.Get/Set/etc +// and converts it to a fuse status. +func unpackXattrErr(err error) fuse.Status { + if err == nil { + return fuse.OK + } + err2, ok := err.(*xattr.Error) + if !ok { + tlog.Warn.Printf("unpackXattrErr: cannot unpack err=%v", err) + return fuse.EIO + } + return fuse.ToStatus(err2.Err) +} diff --git a/internal/fusefrontend/xattr_unit_test.go b/internal/fusefrontend/xattr_unit_test.go new file mode 100644 index 0000000..ea5d3bb --- /dev/null +++ b/internal/fusefrontend/xattr_unit_test.go @@ -0,0 +1,41 @@ +package fusefrontend + +// This file is named "xattr_unit_test.go" because there is also a +// "xattr_integration_test.go" in the test/xattr package. + +import ( + "syscall" + "testing" + + "github.com/rfjakob/gocryptfs/internal/contentenc" + "github.com/rfjakob/gocryptfs/internal/cryptocore" + "github.com/rfjakob/gocryptfs/internal/nametransform" +) + +func newTestFS() *FS { + // Init crypto backend + key := make([]byte, cryptocore.KeyLen) + cCore := cryptocore.New(key, cryptocore.BackendGoGCM, contentenc.DefaultIVBits, true, false) + cEnc := contentenc.New(cCore, contentenc.DefaultBS, false) + nameTransform := nametransform.New(cCore.EMECipher, true, true) + args := Args{} + return NewFS(args, cEnc, nameTransform) +} + +func TestEncryptDecryptXattrName(t *testing.T) { + fs := newTestFS() + _, err := fs.encryptXattrName("xxxx") + if err != syscall.EPERM { + t.Fatalf("Names that don't start with 'user.' should fail") + } + attr1 := "user.foo123456789" + cAttr, err := fs.encryptXattrName(attr1) + if err != nil { + t.Fatal(err) + } + t.Logf("cAttr=%v", cAttr) + attr2, err := fs.decryptXattrName(cAttr) + if attr1 != attr2 { + t.Fatalf("Decrypt mismatch: %v != %v", attr1, attr2) + } +} |