Update FUSE related stuff according to gocryptfs fusefrontend

This commit is contained in:
Matéo Duparc 2022-04-19 19:30:21 +02:00
parent 89966b1aae
commit 71eb2bdf7c
Signed by: hardcoresushi
GPG Key ID: AFE384344A45E13A
6 changed files with 240 additions and 115 deletions

View File

@ -3,7 +3,6 @@ package main
import (
"C"
"syscall"
"golang.org/x/sys/unix"
"libgocryptfs/v2/internal/nametransform"

136
dircache.go Normal file
View File

@ -0,0 +1,136 @@
package main
import (
"log"
"syscall"
"time"
)
const (
// Number of entries in the dirCache.
// 20 entries work well for "git stat" on a small git repo on sshfs.
// Keep in sync with test_helpers.maxCacheFds !
// TODO: How to share this constant without causing an import cycle?
dirCacheSize = 20
// Enable Lookup/Store/Clear debug messages
enableDebugMessages = false
// Enable hit rate statistics printing
enableStats = false
)
type dirCacheEntry struct {
path string
// fd to the directory (opened with O_PATH!)
fd int
// content of gocryptfs.diriv in this directory
iv []byte
}
func (e *dirCacheEntry) Clear() {
// An earlier clear may have already closed the fd, or the cache
// has never been filled (fd is 0 in that case).
// Note: package ensurefds012, imported from main, guarantees that dirCache
// can never get fds 0,1,2.
if e.fd > 0 {
syscall.Close(e.fd)
}
e.fd = -1
e.path = ""
e.iv = nil
}
type dirCache struct {
// Expected length of the stored IVs. Only used for sanity checks.
// Usually set to 16, but 0 in plaintextnames mode.
ivLen int
// Cache entries
entries [dirCacheSize]dirCacheEntry
// Where to store the next entry (index into entries)
nextIndex int
// On the first Lookup(), the expire thread is started, and this flag is set
// to true.
expireThreadRunning bool
// Hit rate stats. Evaluated and reset by the expire thread.
lookups uint64
hits uint64
}
// Clear clears the cache contents.
func (d *dirCache) Clear() {
for i := range d.entries {
d.entries[i].Clear()
}
}
// Store the entry in the cache. The passed "fd" will be Dup()ed, and the caller
// can close their copy at will.
func (d *dirCache) Store(path string, fd int, iv []byte) {
// Note: package ensurefds012, imported from main, guarantees that dirCache
// can never get fds 0,1,2.
if fd <= 0 || len(iv) != d.ivLen {
log.Panicf("Store sanity check failed: fd=%d len=%d", fd, len(iv))
}
e := &d.entries[d.nextIndex]
// Round-robin works well enough
d.nextIndex = (d.nextIndex + 1) % dirCacheSize
// Close the old fd
e.Clear()
fd2, err := syscall.Dup(fd)
if err != nil {
return
}
e.fd = fd2
e.path = string([]byte(path[:]))
e.iv = iv
// expireThread is started on the first Lookup()
if !d.expireThreadRunning {
d.expireThreadRunning = true
go d.expireThread()
}
}
// Lookup checks if relPath is in the cache, and returns an (fd, iv) pair.
// It returns (-1, nil) if not found. The fd is internally Dup()ed and the
// caller must close it when done.
func (d *dirCache) Lookup(path string) (fd int, iv []byte) {
if enableStats {
d.lookups++
}
var e *dirCacheEntry
for i := range d.entries {
e = &d.entries[i]
if e.fd <= 0 {
// Cache slot is empty
continue
}
if path != e.path {
// Not the right path
continue
}
var err error
fd, err = syscall.Dup(e.fd)
if err != nil {
return -1, nil
}
iv = e.iv
break
}
if fd == 0 {
return -1, nil
}
if enableStats {
d.hits++
}
if fd <= 0 || len(iv) != d.ivLen {
log.Panicf("Lookup sanity check failed: fd=%d len=%d", fd, len(iv))
}
return fd, iv
}
// expireThread is started on the first Lookup()
func (d *dirCache) expireThread() {
for {
time.Sleep(60 * time.Second)
d.Clear()
}
}

View File

@ -41,7 +41,7 @@ func mkdirWithIv(dirfd int, cName string, mode uint32) error {
//export gcf_list_dir
func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
volume := OpenedVolumes[sessionID]
parentDirFd, cDirName, err := volume.prepareAtSyscall(dirName)
parentDirFd, cDirName, err := volume.prepareAtSyscallMyself(dirName)
if err != nil {
return nil, nil, 0
}
@ -58,7 +58,7 @@ func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
}
// Get DirIV (stays nil if PlaintextNames is used)
var cachedIV []byte
if !OpenedVolumes[sessionID].plainTextNames {
if !volume.plainTextNames {
// Read the DirIV from disk
cachedIV, err = volume.nameTransform.ReadDirIVAt(fd)
if err != nil {
@ -75,7 +75,7 @@ func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
// silently ignore "gocryptfs.conf" in the top level dir
continue
}
if OpenedVolumes[sessionID].plainTextNames {
if volume.plainTextNames {
plain.WriteString(cipherEntries[i].Name + "\x00")
modes = append(modes, cipherEntries[i].Mode)
continue
@ -96,7 +96,7 @@ func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
// ignore "gocryptfs.longname.*.name"
continue
}
name, err := OpenedVolumes[sessionID].nameTransform.DecryptName(cName, cachedIV)
name, err := volume.nameTransform.DecryptName(cName, cachedIV)
if err != nil {
continue
}
@ -143,7 +143,7 @@ func gcf_mkdir(sessionID int, path string, mode uint32) bool {
// Handle long file name
if nametransform.IsLongContent(cName) {
// Create ".name"
err = OpenedVolumes[sessionID].nameTransform.WriteLongNameAt(dirfd, cName, path)
err = volume.nameTransform.WriteLongNameAt(dirfd, cName, path)
if err != nil {
return false
}
@ -188,7 +188,7 @@ func gcf_mkdir(sessionID int, path string, mode uint32) bool {
//export gcf_rmdir
func gcf_rmdir(sessionID int, relPath string) bool {
volume := OpenedVolumes[sessionID]
parentDirFd, cName, err := volume.openBackingDir(relPath)
parentDirFd, cName, err := volume.prepareAtSyscall(relPath)
if err != nil {
return false
}

11
file.go
View File

@ -370,7 +370,7 @@ func (volume *Volume) truncate(handleID int, newSize uint64) bool {
//export gcf_open_read_mode
func gcf_open_read_mode(sessionID int, path string) int {
volume := OpenedVolumes[sessionID]
dirfd, cName, err := volume.prepareAtSyscall(path)
dirfd, cName, err := volume.prepareAtSyscallMyself(path)
if err != nil {
return -1
}
@ -455,13 +455,14 @@ func gcf_write_file(sessionID, handleID int, offset uint64, data []byte) uint32
//export gcf_close_file
func gcf_close_file(sessionID, handleID int) {
f, ok := OpenedVolumes[sessionID].file_handles[handleID]
volume := OpenedVolumes[sessionID]
f, ok := volume.file_handles[handleID]
if ok {
f.fd.Close()
delete(OpenedVolumes[sessionID].file_handles, handleID)
_, ok := OpenedVolumes[sessionID].fileIDs[handleID]
delete(volume.file_handles, handleID)
_, ok := volume.fileIDs[handleID]
if ok {
delete(OpenedVolumes[sessionID].fileIDs, handleID)
delete(volume.fileIDs, handleID)
}
}
}

View File

@ -2,14 +2,20 @@ package main
import (
"path/filepath"
"strings"
"syscall"
"libgocryptfs/v2/internal/configfile"
"libgocryptfs/v2/internal/nametransform"
"libgocryptfs/v2/internal/syscallcompat"
)
func getParentPath(path string) string {
parent := filepath.Dir(path)
if parent == "." {
return ""
}
return parent
}
// isFiltered - check if plaintext "path" should be forbidden
//
// Prevents name clashes with internal files when file names are not encrypted
@ -26,107 +32,99 @@ func (volume *Volume) isFiltered(path string) bool {
return false
}
func (volume *Volume) openBackingDir(relPath string) (dirfd int, cName string, err error) {
dirRelPath := nametransform.Dir(relPath)
// With PlaintextNames, we don't need to read DirIVs. Easy.
if volume.plainTextNames {
dirfd, err = syscallcompat.OpenDirNofollow(volume.rootCipherDir, dirRelPath)
if err != nil {
return -1, "", err
}
// If relPath is empty, cName is ".".
cName = filepath.Base(relPath)
return dirfd, cName, nil
}
// Open cipherdir (following symlinks)
dirfd, err = syscallcompat.Open(volume.rootCipherDir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
if err != nil {
return -1, "", err
}
// If relPath is empty, cName is ".".
if relPath == "" {
return dirfd, ".", nil
}
// Walk the directory tree
parts := strings.Split(relPath, "/")
for i, name := range parts {
iv, err := volume.nameTransform.ReadDirIVAt(dirfd)
if err != nil {
syscall.Close(dirfd)
return -1, "", err
}
cName, err = volume.nameTransform.EncryptAndHashName(name, iv)
if err != nil {
syscall.Close(dirfd)
return -1, "", err
}
// Last part? We are done.
if i == len(parts)-1 {
break
}
// Not the last part? Descend into next directory.
dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
syscall.Close(dirfd)
if err != nil {
return -1, "", err
}
dirfd = dirfd2
}
return dirfd, cName, nil
func (volume *Volume) prepareAtSyscall(path string) (dirfd int, cName string, err error) {
if path == "" {
return volume.prepareAtSyscallMyself(path)
}
func (volume *Volume) prepareAtSyscall(path string) (dirfd int, cName string, err error) {
// root node itself is special
if path == "" {
return volume.openBackingDir(path)
if volume.isFiltered(path) {
return -1, "", nil
}
var encryptName func(int, string, []byte) (string, error)
if !volume.plainTextNames {
encryptName = func(dirfd int, child string, iv []byte) (cName string, err error) {
// Badname allowed, try to determine filenames
if volume.nameTransform.HaveBadnamePatterns() {
return volume.nameTransform.EncryptAndHashBadName(child, iv, dirfd)
}
return volume.nameTransform.EncryptAndHashName(child, iv)
}
}
child := filepath.Base(path)
parentPath := getParentPath(path)
// Cache lookup
// TODO make it work for plaintextnames as well?
if !volume.plainTextNames {
directory, ok := volume.dirCache[path]
if ok {
if directory.fd > 0 {
cName, err := volume.nameTransform.EncryptAndHashName(filepath.Base(path), directory.iv)
if err != nil {
return -1, "", err
var iv []byte
dirfd, iv = volume.dirCache.Lookup(parentPath)
if dirfd > 0 {
if volume.plainTextNames {
return dirfd, child, nil
}
dirfd, err = syscall.Dup(directory.fd)
var err error
cName, err = encryptName(dirfd, child, iv)
if err != nil {
syscall.Close(dirfd)
return -1, "", err
}
return dirfd, cName, nil
}
}
}
// Slowpath
if volume.isFiltered(path) {
return -1, "", syscall.EPERM
// Slowpath: Open ourselves & read diriv
parentDirfd, myCName, err := volume.prepareAtSyscallMyself(parentPath)
if err != nil {
return
}
dirfd, cName, err = volume.openBackingDir(path)
defer syscall.Close(parentDirfd)
dirfd, err = syscallcompat.Openat(parentDirfd, myCName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
if err != nil {
return -1, "", err
}
// Cache store
if !volume.plainTextNames {
// TODO: openBackingDir already calls ReadDirIVAt(). Avoid duplicate work?
iv, err := volume.nameTransform.ReadDirIVAt(dirfd)
var err error
iv, err = volume.nameTransform.ReadDirIVAt(dirfd)
if err != nil {
syscall.Close(dirfd)
return -1, "", err
}
dirfdDup, err := syscall.Dup(dirfd)
if err == nil {
var pathCopy strings.Builder
pathCopy.WriteString(path)
volume.dirCache[pathCopy.String()] = Directory{dirfdDup, iv}
}
volume.dirCache.Store(parentPath, dirfd, iv)
if volume.plainTextNames {
return dirfd, child, nil
}
cName, err = encryptName(dirfd, child, iv)
if err != nil {
syscall.Close(dirfd)
return -1, "", err
}
return
}
func (volume *Volume) prepareAtSyscallMyself(path string) (dirfd int, cName string, err error) {
dirfd = -1
// Handle root node
if path == "" {
var err error
// Open cipherdir (following symlinks)
dirfd, err = syscallcompat.Open(volume.rootCipherDir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
if err != nil {
return -1, "", err
}
return dirfd, ".", nil
}
// Otherwise convert to prepareAtSyscall of parent node
return volume.prepareAtSyscall(path)
}
// decryptSymlinkTarget: "cData64" is base64-decoded and decrypted
// like file contents (GCM).
// The empty string decrypts to the empty string.

View File

@ -7,7 +7,6 @@ import (
"C"
"os"
"path/filepath"
"strings"
"syscall"
"libgocryptfs/v2/internal/configfile"
@ -18,11 +17,6 @@ import (
"libgocryptfs/v2/internal/syscallcompat"
)
type Directory struct {
fd int
iv []byte
}
type File struct {
fd *os.File
path string
@ -35,12 +29,12 @@ type Volume struct {
nameTransform *nametransform.NameTransform
cryptoCore *cryptocore.CryptoCore
contentEnc *contentenc.ContentEnc
dirCache map[string]Directory
dirCache dirCache
file_handles map[int]File
fileIDs map[int][]byte
}
var OpenedVolumes map[int]Volume
var OpenedVolumes map[int]*Volume
func wipe(d []byte) {
for i := range d {
@ -49,12 +43,6 @@ func wipe(d []byte) {
d = nil
}
func clearDirCache(volumeID int) {
for k := range OpenedVolumes[volumeID].dirCache {
delete(OpenedVolumes[volumeID].dirCache, k)
}
}
func errToBool(err error) bool {
return err == nil
}
@ -85,12 +73,14 @@ func registerNewVolume(rootCipherDir string, masterkey []byte, cf *configfile.Co
)
//copying rootCipherDir
var grcd strings.Builder
grcd.WriteString(rootCipherDir)
newVolume.rootCipherDir = grcd.String()
newVolume.rootCipherDir = string([]byte(rootCipherDir[:]))
ivLen := nametransform.DirIVLen
if newVolume.plainTextNames {
ivLen = 0
}
// New empty caches
newVolume.dirCache = make(map[string]Directory)
newVolume.dirCache = dirCache{ivLen: ivLen}
newVolume.file_handles = make(map[int]File)
newVolume.fileIDs = make(map[int][]byte)
@ -105,9 +95,9 @@ func registerNewVolume(rootCipherDir string, masterkey []byte, cf *configfile.Co
c++
}
if OpenedVolumes == nil {
OpenedVolumes = make(map[int]Volume)
OpenedVolumes = make(map[int]*Volume)
}
OpenedVolumes[volumeID] = newVolume
OpenedVolumes[volumeID] = &newVolume
return volumeID
}
@ -127,11 +117,12 @@ func gcf_init(rootCipherDir string, password, givenScryptHash, returnedScryptHas
//export gcf_close
func gcf_close(volumeID int) {
OpenedVolumes[volumeID].cryptoCore.Wipe()
for handleID := range OpenedVolumes[volumeID].file_handles {
volume := OpenedVolumes[volumeID]
volume.cryptoCore.Wipe()
for handleID := range volume.file_handles {
gcf_close_file(volumeID, handleID)
}
clearDirCache(volumeID)
volume.dirCache.Clear()
delete(OpenedVolumes, volumeID)
}