summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/fusefrontend_reverse/file.go70
-rw-r--r--internal/fusefrontend_reverse/file_api_check.go23
-rw-r--r--internal/fusefrontend_reverse/file_helpers.go62
-rw-r--r--internal/fusefrontend_reverse/node.go86
-rw-r--r--internal/fusefrontend_reverse/node_api_check.go6
-rw-r--r--internal/fusefrontend_reverse/node_helpers.go33
-rw-r--r--internal/fusefrontend_reverse/virtualconf.go55
-rw-r--r--internal/fusefrontend_reverse/virtualnode.go (renamed from internal/fusefrontend_reverse/virtualfile.go)16
8 files changed, 327 insertions, 24 deletions
diff --git a/internal/fusefrontend_reverse/file.go b/internal/fusefrontend_reverse/file.go
new file mode 100644
index 0000000..55f5f80
--- /dev/null
+++ b/internal/fusefrontend_reverse/file.go
@@ -0,0 +1,70 @@
+package fusefrontend_reverse
+
+import (
+ "bytes"
+ "context"
+ "os"
+ "syscall"
+
+ "github.com/hanwen/go-fuse/v2/fs"
+ "github.com/hanwen/go-fuse/v2/fuse"
+
+ "github.com/rfjakob/gocryptfs/internal/contentenc"
+)
+
+type File struct {
+ // Backing FD
+ fd *os.File
+ // File header (contains the IV)
+ header contentenc.FileHeader
+ // IV for block 0
+ block0IV []byte
+ // Content encryption helper
+ contentEnc *contentenc.ContentEnc
+}
+
+// Read - FUSE call
+func (f *File) Read(ctx context.Context, buf []byte, ioff int64) (resultData fuse.ReadResult, errno syscall.Errno) {
+ length := uint64(len(buf))
+ off := uint64(ioff)
+ out := bytes.NewBuffer(buf[:0])
+ var header []byte
+
+ // Synthesize file header
+ if off < contentenc.HeaderLen {
+ header = f.header.Pack()
+ // Truncate to requested part
+ end := int(off) + len(buf)
+ if end > len(header) {
+ end = len(header)
+ }
+ header = header[off:end]
+ // Write into output buffer and adjust offsets
+ out.Write(header)
+ hLen := uint64(len(header))
+ off += hLen
+ length -= hLen
+ }
+
+ // Read actual file data
+ if length > 0 {
+ fileData, err := f.readBackingFile(off, length)
+ if err != nil {
+ return nil, fs.ToErrno(err)
+ }
+ if len(fileData) == 0 {
+ // If we could not read any actual data, we also don't want to
+ // return the file header. An empty file stays empty in encrypted
+ // form.
+ return nil, 0
+ }
+ out.Write(fileData)
+ }
+
+ return fuse.ReadResultData(out.Bytes()), 0
+}
+
+// Release - FUSE call, close file
+func (f *File) Release(context.Context) syscall.Errno {
+ return fs.ToErrno(f.fd.Close())
+}
diff --git a/internal/fusefrontend_reverse/file_api_check.go b/internal/fusefrontend_reverse/file_api_check.go
new file mode 100644
index 0000000..6dd1771
--- /dev/null
+++ b/internal/fusefrontend_reverse/file_api_check.go
@@ -0,0 +1,23 @@
+package fusefrontend_reverse
+
+import (
+ "github.com/hanwen/go-fuse/v2/fs"
+)
+
+// Check that we have implemented the fs.File* interfaces
+var _ = (fs.FileReader)((*File)(nil))
+var _ = (fs.FileReleaser)((*File)(nil))
+
+/* TODO
+var _ = (fs.FileGetattrer)((*File2)(nil))
+var _ = (fs.FileSetattrer)((*File2)(nil))
+var _ = (fs.FileWriter)((*File2)(nil))
+var _ = (fs.FileFsyncer)((*File2)(nil))
+var _ = (fs.FileFlusher)((*File2)(nil))
+var _ = (fs.FileAllocater)((*File2)(nil))
+var _ = (fs.FileLseeker)((*File2)(nil))
+var _ = (fs.FileHandle)((*File2)(nil))
+var _ = (fs.FileGetlker)((*File2)(nil))
+var _ = (fs.FileSetlker)((*File2)(nil))
+var _ = (fs.FileSetlkwer)((*File2)(nil))
+*/
diff --git a/internal/fusefrontend_reverse/file_helpers.go b/internal/fusefrontend_reverse/file_helpers.go
new file mode 100644
index 0000000..f024e69
--- /dev/null
+++ b/internal/fusefrontend_reverse/file_helpers.go
@@ -0,0 +1,62 @@
+package fusefrontend_reverse
+
+import (
+ "bytes"
+ "io"
+ "sync"
+
+ "github.com/rfjakob/gocryptfs/internal/contentenc"
+ "github.com/rfjakob/gocryptfs/internal/pathiv"
+ "github.com/rfjakob/gocryptfs/internal/tlog"
+)
+
+var inodeTable sync.Map
+
+// encryptBlocks - encrypt "plaintext" into a number of ciphertext blocks.
+// "plaintext" must already be block-aligned.
+func (rf *File) encryptBlocks(plaintext []byte, firstBlockNo uint64, fileID []byte, block0IV []byte) []byte {
+ inBuf := bytes.NewBuffer(plaintext)
+ var outBuf bytes.Buffer
+ bs := int(rf.contentEnc.PlainBS())
+ for blockNo := firstBlockNo; inBuf.Len() > 0; blockNo++ {
+ inBlock := inBuf.Next(bs)
+ iv := pathiv.BlockIV(block0IV, blockNo)
+ outBlock := rf.contentEnc.EncryptBlockNonce(inBlock, blockNo, fileID, iv)
+ outBuf.Write(outBlock)
+ }
+ return outBuf.Bytes()
+}
+
+// readBackingFile: read from the backing plaintext file, encrypt it, return the
+// ciphertext.
+// "off" ... ciphertext offset (must be >= HEADER_LEN)
+// "length" ... ciphertext length
+func (f *File) readBackingFile(off uint64, length uint64) (out []byte, err error) {
+ blocks := f.contentEnc.ExplodeCipherRange(off, length)
+
+ // Read the backing plaintext in one go
+ alignedOffset, alignedLength := contentenc.JointPlaintextRange(blocks)
+ plaintext := make([]byte, int(alignedLength))
+ n, err := f.fd.ReadAt(plaintext, int64(alignedOffset))
+ if err != nil && err != io.EOF {
+ tlog.Warn.Printf("readBackingFile: ReadAt: %s", err.Error())
+ return nil, err
+ }
+ // Truncate buffer down to actually read bytes
+ plaintext = plaintext[0:n]
+
+ // Encrypt blocks
+ ciphertext := f.encryptBlocks(plaintext, blocks[0].BlockNo, f.header.ID, f.block0IV)
+
+ // Crop down to the relevant part
+ lenHave := len(ciphertext)
+ skip := blocks[0].Skip
+ endWant := int(skip + length)
+ if lenHave > endWant {
+ out = ciphertext[skip:endWant]
+ } else if lenHave > int(skip) {
+ out = ciphertext[skip:lenHave]
+ } // else: out stays empty, file was smaller than the requested offset
+
+ return out, nil
+}
diff --git a/internal/fusefrontend_reverse/node.go b/internal/fusefrontend_reverse/node.go
index 884a97e..deaf953 100644
--- a/internal/fusefrontend_reverse/node.go
+++ b/internal/fusefrontend_reverse/node.go
@@ -2,6 +2,8 @@ package fusefrontend_reverse
import (
"context"
+ "fmt"
+ "os"
"path/filepath"
"syscall"
@@ -10,8 +12,10 @@ import (
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
- "github.com/rfjakob/gocryptfs/internal/configfile"
+ "github.com/rfjakob/gocryptfs/internal/contentenc"
+ "github.com/rfjakob/gocryptfs/internal/pathiv"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
+ "github.com/rfjakob/gocryptfs/internal/tlog"
)
// Node is a file or directory in the filesystem tree
@@ -33,15 +37,7 @@ func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch
return n.lookupLongnameName(ctx, cName, out)
} else if t == typeConfig {
// gocryptfs.conf
- var err error
- pName = configfile.ConfReverseName
- rn := n.rootNode()
- dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, "")
- if err != nil {
- errno = fs.ToErrno(err)
- return
- }
- defer syscall.Close(dirfd)
+ return n.lookupConf(ctx, out)
} else if t == typeReal {
// real file
dirfd, pName, errno = n.prepareAtSyscall(cName)
@@ -112,3 +108,73 @@ func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) {
cName := filepath.Base(n.Path())
return n.readlink(dirfd, cName, pName)
}
+
+// Open - FUSE call. Open already-existing file.
+//
+// Symlink-safe through Openat().
+func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
+ dirfd, pName, errno := n.prepareAtSyscall("")
+ if errno != 0 {
+ return
+ }
+ defer syscall.Close(dirfd)
+
+ fd, err := syscallcompat.Openat(dirfd, pName, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
+ if err != nil {
+ errno = fs.ToErrno(err)
+ return
+ }
+
+ // Reject access if the file descriptor does not refer to a regular file.
+ var st syscall.Stat_t
+ err = syscall.Fstat(fd, &st)
+ if err != nil {
+ tlog.Warn.Printf("Open: Fstat error: %v", err)
+ syscall.Close(fd)
+ errno = fs.ToErrno(err)
+ return
+ }
+ var a fuse.Attr
+ a.FromStat(&st)
+ if !a.IsRegular() {
+ tlog.Warn.Printf("ino%d: newFile: not a regular file", st.Ino)
+ syscall.Close(fd)
+ errno = syscall.EACCES
+ return
+ }
+ // See if we have that inode number already in the table
+ // (even if Nlink has dropped to 1)
+ var derivedIVs pathiv.FileIVs
+ v, found := inodeTable.Load(st.Ino)
+ if found {
+ tlog.Debug.Printf("ino%d: newFile: found in the inode table", st.Ino)
+ derivedIVs = v.(pathiv.FileIVs)
+ } else {
+ p := n.Path()
+ derivedIVs = pathiv.DeriveFile(p)
+ // Nlink > 1 means there is more than one path to this file.
+ // Store the derived values so we always return the same data,
+ // regardless of the path that is used to access the file.
+ // This means that the first path wins.
+ if st.Nlink > 1 {
+ v, found = inodeTable.LoadOrStore(st.Ino, derivedIVs)
+ if found {
+ // Another thread has stored a different value before we could.
+ derivedIVs = v.(pathiv.FileIVs)
+ } else {
+ tlog.Debug.Printf("ino%d: newFile: Nlink=%d, stored in the inode table", st.Ino, st.Nlink)
+ }
+ }
+ }
+ header := contentenc.FileHeader{
+ Version: contentenc.CurrentVersion,
+ ID: derivedIVs.ID,
+ }
+ fh = &File{
+ fd: os.NewFile(uintptr(fd), fmt.Sprintf("fd%d", fd)),
+ header: header,
+ block0IV: derivedIVs.Block0IV,
+ contentEnc: n.rootNode().contentEnc,
+ }
+ return
+}
diff --git a/internal/fusefrontend_reverse/node_api_check.go b/internal/fusefrontend_reverse/node_api_check.go
index e926fc3..875c9b9 100644
--- a/internal/fusefrontend_reverse/node_api_check.go
+++ b/internal/fusefrontend_reverse/node_api_check.go
@@ -9,11 +9,10 @@ var _ = (fs.NodeGetattrer)((*Node)(nil))
var _ = (fs.NodeLookuper)((*Node)(nil))
var _ = (fs.NodeReaddirer)((*Node)(nil))
var _ = (fs.NodeReadlinker)((*Node)(nil))
-
-/* TODO
var _ = (fs.NodeOpener)((*Node)(nil))
+
+/*
var _ = (fs.NodeStatfser)((*Node)(nil))
-var _ = (fs.NodeMknoder)((*Node)(nil))
var _ = (fs.NodeGetxattrer)((*Node)(nil))
var _ = (fs.NodeListxattrer)((*Node)(nil))
*/
@@ -23,6 +22,7 @@ var _ = (fs.NodeOpendirer)((*Node)(nil))
*/
/* Will not implement these - reverse mode is read-only!
+var _ = (fs.NodeMknoder)((*Node)(nil))
var _ = (fs.NodeCreater)((*Node)(nil))
var _ = (fs.NodeMkdirer)((*Node)(nil))
var _ = (fs.NodeRmdirer)((*Node)(nil))
diff --git a/internal/fusefrontend_reverse/node_helpers.go b/internal/fusefrontend_reverse/node_helpers.go
index 7669a17..fd0abfc 100644
--- a/internal/fusefrontend_reverse/node_helpers.go
+++ b/internal/fusefrontend_reverse/node_helpers.go
@@ -10,6 +10,7 @@ import (
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
+ "github.com/rfjakob/gocryptfs/internal/configfile"
"github.com/rfjakob/gocryptfs/internal/pathiv"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
)
@@ -115,8 +116,8 @@ func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fus
errno = fs.ToErrno(err)
return
}
- var vf *virtualFile
- vf, errno = n.newVirtualFile([]byte(cFullname), st, inoTagNameFile)
+ var vf *VirtualMemNode
+ vf, errno = n.newVirtualMemNode([]byte(cFullname), st, inoTagNameFile)
if errno != 0 {
return nil, errno
}
@@ -141,8 +142,8 @@ func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inod
return
}
content := pathiv.Derive(n.Path(), pathiv.PurposeDirIV)
- var vf *virtualFile
- vf, errno = n.newVirtualFile(content, st, inoTagDirIV)
+ var vf *VirtualMemNode
+ vf, errno = n.newVirtualMemNode(content, st, inoTagDirIV)
if errno != 0 {
return nil, errno
}
@@ -153,6 +154,30 @@ func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inod
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)
+ // Create child node
+ id := fs.StableAttr{
+ Mode: uint32(st.Mode),
+ Gen: 1,
+ Ino: 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)
diff --git a/internal/fusefrontend_reverse/virtualconf.go b/internal/fusefrontend_reverse/virtualconf.go
new file mode 100644
index 0000000..8620f6d
--- /dev/null
+++ b/internal/fusefrontend_reverse/virtualconf.go
@@ -0,0 +1,55 @@
+package fusefrontend_reverse
+
+import (
+ "context"
+ "sync"
+ "syscall"
+
+ "github.com/hanwen/go-fuse/v2/fs"
+ "github.com/hanwen/go-fuse/v2/fuse"
+)
+
+var _ = (fs.NodeOpener)((*VirtualConfNode)(nil))
+
+type VirtualConfNode struct {
+ fs.Inode
+
+ path string
+}
+
+func (n *VirtualConfNode) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
+ fd, err := syscall.Open(n.path, syscall.O_RDONLY, 0)
+ if err != nil {
+ errno = fs.ToErrno(err)
+ return
+ }
+ fh = &VirtualConfFile{fd: fd}
+ return
+}
+
+// Check that we have implemented the fs.File* interfaces
+var _ = (fs.FileReader)((*VirtualConfFile)(nil))
+var _ = (fs.FileReleaser)((*VirtualConfFile)(nil))
+
+type VirtualConfFile struct {
+ mu sync.Mutex
+ fd int
+}
+
+func (f *VirtualConfFile) Read(ctx context.Context, buf []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ res = fuse.ReadResultFd(uintptr(f.fd), off, len(buf))
+ return
+}
+
+func (f *VirtualConfFile) Release(ctx context.Context) syscall.Errno {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ if f.fd != -1 {
+ err := syscall.Close(f.fd)
+ f.fd = -1
+ return fs.ToErrno(err)
+ }
+ return syscall.EBADF
+}
diff --git a/internal/fusefrontend_reverse/virtualfile.go b/internal/fusefrontend_reverse/virtualnode.go
index a7f6913..2ee9548 100644
--- a/internal/fusefrontend_reverse/virtualfile.go
+++ b/internal/fusefrontend_reverse/virtualnode.go
@@ -60,7 +60,9 @@ func (n *Node) lookupFileType(cName string) fileType {
return typeReal
}
-type virtualFile struct {
+// VirtualMemNode is an in-memory node that does not have a representation
+// on disk.
+type VirtualMemNode struct {
fs.Inode
// file content
@@ -69,12 +71,12 @@ type virtualFile struct {
attr fuse.Attr
}
-// newVirtualFile creates a new in-memory file that does not have a representation
+// newVirtualMemNode creates a new in-memory file that does not have a representation
// on disk. "content" is the file content. Timestamps and file owner are copied
// from "parentFile" (file descriptor).
// For a "gocryptfs.diriv" file, you would use the parent directory as
// "parentFile".
-func (n *Node) newVirtualFile(content []byte, parentStat *syscall.Stat_t, inoTag uint8) (vf *virtualFile, errno syscall.Errno) {
+func (n *Node) newVirtualMemNode(content []byte, parentStat *syscall.Stat_t, inoTag uint8) (vf *VirtualMemNode, errno syscall.Errno) {
if inoTag == 0 {
log.Panicf("BUG: inoTag for virtual file is zero - this will cause ino collisions!")
}
@@ -90,23 +92,23 @@ func (n *Node) newVirtualFile(content []byte, parentStat *syscall.Stat_t, inoTag
var a fuse.Attr
a.FromStat(st)
- vf = &virtualFile{content: content, attr: a}
+ vf = &VirtualMemNode{content: content, attr: a}
return
}
// Open - FUSE call
-func (f *virtualFile) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
+func (f *VirtualMemNode) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
return nil, fuse.FOPEN_KEEP_CACHE, 0
}
// GetAttr - FUSE call
-func (f *virtualFile) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
+func (f *VirtualMemNode) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Attr = f.attr
return 0
}
// Read - FUSE call
-func (f *virtualFile) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
+func (f *VirtualMemNode) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
end := int(off) + len(dest)
if end > len(f.content) {
end = len(f.content)