1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
|
package fusefrontend_reverse
import (
"context"
"log"
"path/filepath"
"syscall"
"golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/rfjakob/gocryptfs/v2/internal/configfile"
"github.com/rfjakob/gocryptfs/v2/internal/pathiv"
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
)
const (
// File names are padded to 16-byte multiples, encrypted and
// base64-encoded. We can encode at most 176 bytes to stay below the 255
// bytes limit:
// * base64(176 bytes) = 235 bytes
// * base64(192 bytes) = 256 bytes (over 255!)
// But the PKCS#7 padding is at least one byte. This means we can only use
// 175 bytes for the file name.
)
// translateSize translates the ciphertext size in `out` into plaintext size.
func (n *Node) translateSize(dirfd int, cName string, pName string, out *fuse.Attr) {
if out.IsRegular() {
rn := n.rootNode()
out.Size = rn.contentEnc.PlainSizeToCipherSize(out.Size)
} else if out.IsSymlink() {
cLink, _ := n.readlink(dirfd, cName, pName)
out.Size = uint64(len(cLink))
}
}
// Path returns the relative plaintext path of this node
func (n *Node) Path() string {
return n.Inode.Path(n.Root())
}
// rootNode returns the Root Node of the filesystem.
func (n *Node) rootNode() *RootNode {
return n.Root().Operations().(*RootNode)
}
// dirfdPlus gets filled out as we gather information about a node
type dirfdPlus struct {
// fd to the directory, opened with O_DIRECTORY|O_PATH
dirfd int
// Relative plaintext path
pPath string
// Plaintext basename: filepath.Base(pPath)
pName string
// Relative ciphertext path
cPath string
// Ciphertext basename: filepath.Base(cPath)
cName string
}
// prepareAtSyscall returns a (dirfd, cName) pair that can be used
// with the "___at" family of system calls (openat, fstatat, unlinkat...) to
// access the backing encrypted directory.
//
// If you pass a `child` file name, the (dirfd, cName) pair will refer to
// a child of this node.
// If `child` is empty, the (dirfd, cName) pair refers to this node itself.
func (n *Node) prepareAtSyscall(child string) (d *dirfdPlus, errno syscall.Errno) {
cPath := n.Path()
if child != "" {
cPath = filepath.Join(cPath, child)
}
rn := n.rootNode()
dirfd, pPath, err := rn.openBackingDir(cPath)
if err != nil {
errno = fs.ToErrno(err)
}
d = &dirfdPlus{
dirfd: dirfd,
pPath: pPath,
pName: filepath.Base(pPath),
cPath: cPath,
cName: filepath.Base(cPath),
}
return
}
// newChild attaches a new child inode to n.
// The passed-in `st` will be modified to get a unique inode number.
//
// This function is not used for virtual files. See lookupLongnameName(),
// lookupDiriv() instead.
func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.EntryOut) *fs.Inode {
rn := n.rootNode()
isOtherFilesystem := (uint64(st.Dev) != rn.rootDev)
// Get unique inode number
rn.inoMap.TranslateStat(st)
out.Attr.FromStat(st)
// Create child node
id := rn.uniqueStableAttr(uint32(st.Mode), st.Ino)
node := &Node{
isOtherFilesystem: isOtherFilesystem,
}
return n.NewInode(ctx, node, id)
}
// isRoot returns true if this node is the root node
func (n *Node) isRoot() bool {
rn := n.rootNode()
return &rn.Node == n
}
func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
d, errno := n.prepareAtSyscall("")
if errno != 0 {
return
}
defer syscall.Close(d.dirfd)
// Find the file the gocryptfs.longname.XYZ.name file belongs to in the
// directory listing
fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
if err != nil {
errno = fs.ToErrno(err)
return
}
defer syscall.Close(fd)
rn := n.rootNode()
diriv := rn.deriveDirIV(d.cPath)
pName, cFullname, errno := rn.findLongnameParent(fd, diriv, nameFile)
if errno != 0 {
return
}
if rn.isExcludedPlain(filepath.Join(d.cPath, pName)) {
errno = syscall.EPERM
return
}
// Get attrs from parent file
st, err := syscallcompat.Fstatat2(fd, pName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
errno = fs.ToErrno(err)
return
}
var vf *VirtualMemNode
vf, errno = n.newVirtualMemNode([]byte(cFullname), st, inoTagNameFile)
if errno != 0 {
return nil, errno
}
out.Attr = vf.attr
// Create child node
id := rn.uniqueStableAttr(uint32(vf.attr.Mode), vf.attr.Ino)
ch = n.NewInode(ctx, vf, id)
return
}
// lookupDiriv returns a new Inode for a gocryptfs.diriv file inside `n`.
func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
rn := n.rootNode()
if rn.args.DeterministicNames {
log.Panic("BUG: lookupDiriv called but DeterministicNames is set")
}
d, errno := n.prepareAtSyscall("")
if errno != 0 {
return
}
defer syscall.Close(d.dirfd)
st, err := syscallcompat.Fstatat2(d.dirfd, d.pName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
errno = fs.ToErrno(err)
return
}
content := rn.deriveDirIV(d.cPath)
var vf *VirtualMemNode
vf, errno = n.newVirtualMemNode(content, st, inoTagDirIV)
if errno != 0 {
return nil, errno
}
out.Attr = vf.attr
// Create child node
id := rn.uniqueStableAttr(uint32(vf.attr.Mode), vf.attr.Ino)
ch = n.NewInode(ctx, vf, id)
return
}
// lookupConf returns a new Inode for the gocryptfs.conf file
func (n *Node) lookupConf(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
rn := n.rootNode()
p := filepath.Join(rn.args.Cipherdir, configfile.ConfReverseName)
var st syscall.Stat_t
err := syscall.Stat(p, &st)
if err != nil {
errno = fs.ToErrno(err)
return
}
// Get unique inode number
rn.inoMap.TranslateStat(&st)
out.Attr.FromStat(&st)
if rn.args.ForceOwner != nil {
out.Owner = *rn.args.ForceOwner
}
// Create child node
id := rn.uniqueStableAttr(uint32(st.Mode), st.Ino)
node := &VirtualConfNode{path: p}
ch = n.NewInode(ctx, node, id)
return
}
// readlink reads and encrypts a symlink. Used by Readlink, Getattr, Lookup.
func (n *Node) readlink(dirfd int, cName string, pName string) (out []byte, errno syscall.Errno) {
plainTarget, err := syscallcompat.Readlinkat(dirfd, pName)
if err != nil {
errno = fs.ToErrno(err)
return
}
rn := n.rootNode()
if rn.args.PlaintextNames {
return []byte(plainTarget), 0
}
// Nonce is derived from the relative *ciphertext* path
p := filepath.Join(n.Path(), cName)
nonce := pathiv.Derive(p, pathiv.PurposeSymlinkIV)
// Symlinks are encrypted like file contents and base64-encoded
cBinTarget := rn.contentEnc.EncryptBlockNonce([]byte(plainTarget), 0, nil, nonce)
cTarget := rn.nameTransform.B64EncodeToString(cBinTarget)
// The kernel will reject a symlink target above 4096 chars and return
// and I/O error to the user. Better emit the proper error ourselves.
if len(cTarget) > syscallcompat.PATH_MAX {
errno = syscall.ENAMETOOLONG
return
}
return []byte(cTarget), 0
}
|