2018-03-18 17:43:38 +01:00
|
|
|
// Package fusefrontend interfaces directly with the go-fuse library.
|
|
|
|
package fusefrontend
|
|
|
|
|
|
|
|
import (
|
2019-01-02 20:48:46 +01:00
|
|
|
"fmt"
|
|
|
|
"runtime"
|
2018-03-18 17:43:38 +01:00
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
|
2019-01-02 16:58:48 +01:00
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
|
2018-03-18 17:43:38 +01:00
|
|
|
"github.com/hanwen/go-fuse/fuse"
|
|
|
|
|
2019-01-02 16:58:48 +01:00
|
|
|
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
2018-03-18 17:43:38 +01:00
|
|
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
|
|
|
)
|
|
|
|
|
2018-06-12 21:07:00 +02:00
|
|
|
const _EOPNOTSUPP = fuse.Status(syscall.EOPNOTSUPP)
|
|
|
|
|
2018-03-18 17:43:38 +01:00
|
|
|
// xattr names are encrypted like file names, but with a fixed IV.
|
2018-06-12 21:07:00 +02:00
|
|
|
// Padded with "_xx" for length 16.
|
2018-03-18 17:43:38 +01:00
|
|
|
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."
|
|
|
|
|
2018-10-01 21:39:19 +02:00
|
|
|
// GetXAttr - FUSE call. Reads the value of extended attribute "attr".
|
2018-11-04 21:29:17 +01:00
|
|
|
//
|
2019-01-02 16:58:48 +01:00
|
|
|
// This function is symlink-safe through Fgetxattr.
|
2018-11-11 17:43:48 +01:00
|
|
|
func (fs *FS) GetXAttr(relPath string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
|
|
|
|
if fs.isFiltered(relPath) {
|
2018-03-18 17:43:38 +01:00
|
|
|
return nil, fuse.EPERM
|
|
|
|
}
|
2018-04-17 20:33:04 +02:00
|
|
|
if disallowedXAttrName(attr) {
|
2018-06-12 21:07:00 +02:00
|
|
|
return nil, _EOPNOTSUPP
|
2018-04-02 18:59:02 +02:00
|
|
|
}
|
2019-01-02 16:58:48 +01:00
|
|
|
|
2019-01-02 20:48:46 +01:00
|
|
|
// O_NONBLOCK to not block on FIFOs.
|
|
|
|
fd, err := fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_NONBLOCK)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fuse.ToStatus(err)
|
2018-03-18 17:43:38 +01:00
|
|
|
}
|
2019-01-02 20:48:46 +01:00
|
|
|
defer syscall.Close(fd)
|
2019-01-02 16:58:48 +01:00
|
|
|
|
|
|
|
cAttr := fs.encryptXattrName(attr)
|
|
|
|
|
|
|
|
cData, err := syscallcompat.Fgetxattr(fd, cAttr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fuse.ToStatus(err)
|
|
|
|
}
|
|
|
|
|
2018-11-11 17:43:48 +01:00
|
|
|
data, err := fs.decryptXattrValue(cData)
|
2018-03-18 17:43:38 +01:00
|
|
|
if err != nil {
|
|
|
|
tlog.Warn.Printf("GetXAttr: %v", err)
|
|
|
|
return nil, fuse.EIO
|
|
|
|
}
|
2018-05-07 22:01:36 +02:00
|
|
|
return data, fuse.OK
|
2018-03-18 17:43:38 +01:00
|
|
|
}
|
|
|
|
|
2018-11-11 17:57:24 +01:00
|
|
|
// SetXAttr - FUSE call. Set extended attribute.
|
2018-11-04 22:31:55 +01:00
|
|
|
//
|
2019-01-02 16:58:48 +01:00
|
|
|
// This function is symlink-safe through Fsetxattr.
|
2018-11-11 17:57:24 +01:00
|
|
|
func (fs *FS) SetXAttr(relPath string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status {
|
|
|
|
if fs.isFiltered(relPath) {
|
2018-03-18 17:43:38 +01:00
|
|
|
return fuse.EPERM
|
|
|
|
}
|
2018-04-17 20:33:04 +02:00
|
|
|
if disallowedXAttrName(attr) {
|
2018-05-15 23:00:47 +02:00
|
|
|
return _EOPNOTSUPP
|
2018-03-18 17:43:38 +01:00
|
|
|
}
|
2019-01-02 16:58:48 +01:00
|
|
|
|
2019-01-02 20:48:46 +01:00
|
|
|
// O_NONBLOCK to not block on FIFOs.
|
|
|
|
fd, err := fs.openBackingFile(relPath, syscall.O_WRONLY|syscall.O_NONBLOCK)
|
2019-01-04 22:22:24 +01:00
|
|
|
// Directories cannot be opened read-write. Retry.
|
|
|
|
if err == syscall.EISDIR {
|
|
|
|
fd, err = fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK)
|
|
|
|
}
|
2019-01-02 20:48:46 +01:00
|
|
|
if err != nil {
|
|
|
|
return fuse.ToStatus(err)
|
2019-01-02 16:58:48 +01:00
|
|
|
}
|
2019-01-02 20:48:46 +01:00
|
|
|
defer syscall.Close(fd)
|
2019-01-02 16:58:48 +01:00
|
|
|
|
2018-05-01 18:46:51 +02:00
|
|
|
flags = filterXattrSetFlags(flags)
|
2018-04-17 20:33:04 +02:00
|
|
|
cAttr := fs.encryptXattrName(attr)
|
2018-05-07 22:01:36 +02:00
|
|
|
cData := fs.encryptXattrValue(data)
|
2019-01-02 16:58:48 +01:00
|
|
|
|
2019-01-02 20:48:46 +01:00
|
|
|
err = unix.Fsetxattr(fd, cAttr, cData, flags)
|
2019-01-02 16:58:48 +01:00
|
|
|
if err != nil {
|
|
|
|
return fuse.ToStatus(err)
|
|
|
|
}
|
|
|
|
return fuse.OK
|
2018-03-18 17:43:38 +01:00
|
|
|
}
|
|
|
|
|
2018-11-04 22:05:38 +01:00
|
|
|
// RemoveXAttr - FUSE call.
|
|
|
|
//
|
2019-01-02 16:58:48 +01:00
|
|
|
// This function is symlink-safe through Fremovexattr.
|
2018-11-11 18:04:44 +01:00
|
|
|
func (fs *FS) RemoveXAttr(relPath string, attr string, context *fuse.Context) fuse.Status {
|
|
|
|
if fs.isFiltered(relPath) {
|
2018-03-18 17:43:38 +01:00
|
|
|
return fuse.EPERM
|
|
|
|
}
|
2018-04-17 20:33:04 +02:00
|
|
|
if disallowedXAttrName(attr) {
|
2018-05-15 23:00:47 +02:00
|
|
|
return _EOPNOTSUPP
|
2018-03-18 17:43:38 +01:00
|
|
|
}
|
2019-01-02 16:58:48 +01:00
|
|
|
|
2019-01-02 20:48:46 +01:00
|
|
|
// O_NONBLOCK to not block on FIFOs.
|
|
|
|
fd, err := fs.openBackingFile(relPath, syscall.O_WRONLY|syscall.O_NONBLOCK)
|
2019-01-04 22:22:24 +01:00
|
|
|
// Directories cannot be opened read-write. Retry.
|
|
|
|
if err == syscall.EISDIR {
|
|
|
|
fd, err = fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK)
|
|
|
|
}
|
2019-01-02 20:48:46 +01:00
|
|
|
if err != nil {
|
|
|
|
return fuse.ToStatus(err)
|
2019-01-02 16:58:48 +01:00
|
|
|
}
|
2019-01-02 20:48:46 +01:00
|
|
|
defer syscall.Close(fd)
|
2019-01-02 16:58:48 +01:00
|
|
|
|
2018-04-17 20:33:04 +02:00
|
|
|
cAttr := fs.encryptXattrName(attr)
|
2019-01-02 20:48:46 +01:00
|
|
|
err = unix.Fremovexattr(fd, cAttr)
|
2019-01-02 16:58:48 +01:00
|
|
|
if err != nil {
|
|
|
|
return fuse.ToStatus(err)
|
|
|
|
}
|
|
|
|
return fuse.OK
|
2018-03-18 17:43:38 +01:00
|
|
|
}
|
|
|
|
|
2018-11-11 18:27:37 +01:00
|
|
|
// ListXAttr - FUSE call. Lists extended attributes on the file at "relPath".
|
2018-11-04 21:29:17 +01:00
|
|
|
//
|
2019-01-02 16:58:48 +01:00
|
|
|
// This function is symlink-safe through Flistxattr.
|
2018-11-11 18:27:37 +01:00
|
|
|
func (fs *FS) ListXAttr(relPath string, context *fuse.Context) ([]string, fuse.Status) {
|
|
|
|
if fs.isFiltered(relPath) {
|
2018-03-18 17:43:38 +01:00
|
|
|
return nil, fuse.EPERM
|
|
|
|
}
|
2019-01-02 20:48:46 +01:00
|
|
|
var cNames []string
|
|
|
|
var err error
|
|
|
|
if runtime.GOOS == "linux" {
|
|
|
|
dirfd, cName, err2 := fs.openBackingDir(relPath)
|
|
|
|
if err2 != nil {
|
|
|
|
return nil, fuse.ToStatus(err2)
|
|
|
|
}
|
|
|
|
defer syscall.Close(dirfd)
|
|
|
|
procPath := fmt.Sprintf("/proc/self/fd/%d/%s", dirfd, cName)
|
|
|
|
cNames, err = syscallcompat.Llistxattr(procPath)
|
|
|
|
} else {
|
|
|
|
// O_NONBLOCK to not block on FIFOs.
|
2019-01-02 22:19:59 +01:00
|
|
|
fd, err2 := fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_NONBLOCK)
|
2019-01-02 20:48:46 +01:00
|
|
|
// On a symlink, openBackingFile fails with ELOOP. Let's pretend there
|
|
|
|
// can be no xattrs on symlinks, and always return an empty result.
|
|
|
|
if err2 == syscall.ELOOP {
|
|
|
|
return nil, fuse.OK
|
|
|
|
}
|
|
|
|
if err2 != nil {
|
|
|
|
return nil, fuse.ToStatus(err2)
|
|
|
|
}
|
|
|
|
defer syscall.Close(fd)
|
|
|
|
cNames, err = syscallcompat.Flistxattr(fd)
|
2018-03-18 17:43:38 +01:00
|
|
|
}
|
2019-01-02 16:58:48 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, fuse.ToStatus(err)
|
|
|
|
}
|
|
|
|
|
2018-03-18 17:43:38 +01:00
|
|
|
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)
|
2018-07-01 15:48:53 +02:00
|
|
|
fs.reportMitigatedCorruption(curName)
|
2018-03-18 17:43:38 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
names = append(names, name)
|
|
|
|
}
|
|
|
|
return names, fuse.OK
|
|
|
|
}
|
|
|
|
|
|
|
|
// encryptXattrName transforms "user.foo" to "user.gocryptfs.a5sAd4XAa47f5as6dAf"
|
2018-04-17 20:33:04 +02:00
|
|
|
func (fs *FS) encryptXattrName(attr string) (cAttr string) {
|
2018-03-18 17:43:38 +01:00
|
|
|
// xattr names are encrypted like file names, but with a fixed IV.
|
|
|
|
cAttr = xattrStorePrefix + fs.nameTransform.EncryptName(attr, xattrNameIV)
|
2018-04-17 20:33:04 +02:00
|
|
|
return cAttr
|
2018-03-18 17:43:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-05-07 22:01:36 +02:00
|
|
|
// 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 (fs *FS) encryptXattrValue(data []byte) (cData []byte) {
|
|
|
|
if len(data) == 0 {
|
|
|
|
return []byte{}
|
|
|
|
}
|
|
|
|
return fs.contentEnc.EncryptBlock(data, 0, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// decryptXattrValue decrypts the xattr value "cData".
|
|
|
|
func (fs *FS) decryptXattrValue(cData []byte) (data []byte, err error) {
|
|
|
|
if len(cData) == 0 {
|
|
|
|
return []byte{}, nil
|
|
|
|
}
|
|
|
|
data, err1 := fs.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 := fs.nameTransform.B64.DecodeString(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 fs.contentEnc.DecryptBlock([]byte(cData), 0, nil)
|
|
|
|
}
|