// Package fusefrontend interfaces directly with the go-fuse library. package fusefrontend import ( "fmt" "runtime" "strings" "syscall" "golang.org/x/sys/unix" "github.com/hanwen/go-fuse/fuse" "github.com/rfjakob/gocryptfs/internal/syscallcompat" "github.com/rfjakob/gocryptfs/internal/tlog" ) 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. func (fs *FS) GetXAttr(relPath string, attr string, context *fuse.Context) ([]byte, fuse.Status) { if fs.isFiltered(relPath) { return nil, fuse.EPERM } if disallowedXAttrName(attr) { return nil, _EOPNOTSUPP } // 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) } defer syscall.Close(fd) cAttr := fs.encryptXattrName(attr) cData, err := syscallcompat.Fgetxattr(fd, cAttr) if err != nil { return nil, fuse.ToStatus(err) } data, err := fs.decryptXattrValue(cData) if err != nil { tlog.Warn.Printf("GetXAttr: %v", err) return nil, fuse.EIO } return data, fuse.OK } // SetXAttr - FUSE call. Set extended attribute. // // This function is symlink-safe through Fsetxattr. func (fs *FS) SetXAttr(relPath string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status { if fs.isFiltered(relPath) { return fuse.EPERM } if disallowedXAttrName(attr) { return _EOPNOTSUPP } // O_NONBLOCK to not block on FIFOs. fd, err := fs.openBackingFile(relPath, syscall.O_WRONLY|syscall.O_NONBLOCK) if err != nil { return fuse.ToStatus(err) } defer syscall.Close(fd) flags = filterXattrSetFlags(flags) cAttr := fs.encryptXattrName(attr) cData := fs.encryptXattrValue(data) err = unix.Fsetxattr(fd, cAttr, cData, flags) if err != nil { return fuse.ToStatus(err) } return fuse.OK } // RemoveXAttr - FUSE call. // // This function is symlink-safe through Fremovexattr. func (fs *FS) RemoveXAttr(relPath string, attr string, context *fuse.Context) fuse.Status { if fs.isFiltered(relPath) { return fuse.EPERM } if disallowedXAttrName(attr) { return _EOPNOTSUPP } // O_NONBLOCK to not block on FIFOs. fd, err := fs.openBackingFile(relPath, syscall.O_WRONLY|syscall.O_NONBLOCK) if err != nil { return fuse.ToStatus(err) } defer syscall.Close(fd) cAttr := fs.encryptXattrName(attr) err = unix.Fremovexattr(fd, cAttr) if err != nil { return fuse.ToStatus(err) } return fuse.OK } // ListXAttr - FUSE call. Lists extended attributes on the file at "relPath". // // This function is symlink-safe through Flistxattr. func (fs *FS) ListXAttr(relPath string, context *fuse.Context) ([]string, fuse.Status) { if fs.isFiltered(relPath) { return nil, fuse.EPERM } 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. fd, err2 := fs.openBackingFile(relPath, syscall.O_RDONLY|syscall.O_NONBLOCK) // 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) } if err != nil { return nil, fuse.ToStatus(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) fs.reportMitigatedCorruption(curName) 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) { // xattr names are encrypted like file names, but with a fixed IV. cAttr = xattrStorePrefix + fs.nameTransform.EncryptName(attr, xattrNameIV) return cAttr } 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 } // 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) }