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 ( import (
"C" "C"
"syscall" "syscall"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"libgocryptfs/v2/internal/nametransform" "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 //export gcf_list_dir
func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) { func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
volume := OpenedVolumes[sessionID] volume := OpenedVolumes[sessionID]
parentDirFd, cDirName, err := volume.prepareAtSyscall(dirName) parentDirFd, cDirName, err := volume.prepareAtSyscallMyself(dirName)
if err != nil { if err != nil {
return nil, nil, 0 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) // Get DirIV (stays nil if PlaintextNames is used)
var cachedIV []byte var cachedIV []byte
if !OpenedVolumes[sessionID].plainTextNames { if !volume.plainTextNames {
// Read the DirIV from disk // Read the DirIV from disk
cachedIV, err = volume.nameTransform.ReadDirIVAt(fd) cachedIV, err = volume.nameTransform.ReadDirIVAt(fd)
if err != nil { 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 // silently ignore "gocryptfs.conf" in the top level dir
continue continue
} }
if OpenedVolumes[sessionID].plainTextNames { if volume.plainTextNames {
plain.WriteString(cipherEntries[i].Name + "\x00") plain.WriteString(cipherEntries[i].Name + "\x00")
modes = append(modes, cipherEntries[i].Mode) modes = append(modes, cipherEntries[i].Mode)
continue continue
@ -96,7 +96,7 @@ func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
// ignore "gocryptfs.longname.*.name" // ignore "gocryptfs.longname.*.name"
continue continue
} }
name, err := OpenedVolumes[sessionID].nameTransform.DecryptName(cName, cachedIV) name, err := volume.nameTransform.DecryptName(cName, cachedIV)
if err != nil { if err != nil {
continue continue
} }
@ -143,7 +143,7 @@ func gcf_mkdir(sessionID int, path string, mode uint32) bool {
// Handle long file name // Handle long file name
if nametransform.IsLongContent(cName) { if nametransform.IsLongContent(cName) {
// Create ".name" // Create ".name"
err = OpenedVolumes[sessionID].nameTransform.WriteLongNameAt(dirfd, cName, path) err = volume.nameTransform.WriteLongNameAt(dirfd, cName, path)
if err != nil { if err != nil {
return false return false
} }
@ -188,7 +188,7 @@ func gcf_mkdir(sessionID int, path string, mode uint32) bool {
//export gcf_rmdir //export gcf_rmdir
func gcf_rmdir(sessionID int, relPath string) bool { func gcf_rmdir(sessionID int, relPath string) bool {
volume := OpenedVolumes[sessionID] volume := OpenedVolumes[sessionID]
parentDirFd, cName, err := volume.openBackingDir(relPath) parentDirFd, cName, err := volume.prepareAtSyscall(relPath)
if err != nil { if err != nil {
return false 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 //export gcf_open_read_mode
func gcf_open_read_mode(sessionID int, path string) int { func gcf_open_read_mode(sessionID int, path string) int {
volume := OpenedVolumes[sessionID] volume := OpenedVolumes[sessionID]
dirfd, cName, err := volume.prepareAtSyscall(path) dirfd, cName, err := volume.prepareAtSyscallMyself(path)
if err != nil { if err != nil {
return -1 return -1
} }
@ -455,13 +455,14 @@ func gcf_write_file(sessionID, handleID int, offset uint64, data []byte) uint32
//export gcf_close_file //export gcf_close_file
func gcf_close_file(sessionID, handleID int) { 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 { if ok {
f.fd.Close() f.fd.Close()
delete(OpenedVolumes[sessionID].file_handles, handleID) delete(volume.file_handles, handleID)
_, ok := OpenedVolumes[sessionID].fileIDs[handleID] _, ok := volume.fileIDs[handleID]
if ok { if ok {
delete(OpenedVolumes[sessionID].fileIDs, handleID) delete(volume.fileIDs, handleID)
} }
} }
} }

View File

@ -2,14 +2,20 @@ package main
import ( import (
"path/filepath" "path/filepath"
"strings"
"syscall" "syscall"
"libgocryptfs/v2/internal/configfile" "libgocryptfs/v2/internal/configfile"
"libgocryptfs/v2/internal/nametransform"
"libgocryptfs/v2/internal/syscallcompat" "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 // isFiltered - check if plaintext "path" should be forbidden
// //
// Prevents name clashes with internal files when file names are not encrypted // 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 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) { func (volume *Volume) prepareAtSyscall(path string) (dirfd int, cName string, err error) {
// root node itself is special
if path == "" { if path == "" {
return volume.openBackingDir(path) return volume.prepareAtSyscallMyself(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 // Cache lookup
// TODO make it work for plaintextnames as well? var iv []byte
if !volume.plainTextNames { dirfd, iv = volume.dirCache.Lookup(parentPath)
directory, ok := volume.dirCache[path] if dirfd > 0 {
if ok { if volume.plainTextNames {
if directory.fd > 0 { return dirfd, child, nil
cName, err := volume.nameTransform.EncryptAndHashName(filepath.Base(path), directory.iv)
if err != nil {
return -1, "", err
}
dirfd, err = syscall.Dup(directory.fd)
if err != nil {
return -1, "", err
}
return dirfd, cName, nil
}
} }
var err error
cName, err = encryptName(dirfd, child, iv)
if err != nil {
syscall.Close(dirfd)
return -1, "", err
}
return dirfd, cName, nil
} }
// Slowpath // Slowpath: Open ourselves & read diriv
if volume.isFiltered(path) { parentDirfd, myCName, err := volume.prepareAtSyscallMyself(parentPath)
return -1, "", syscall.EPERM 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 { if err != nil {
return -1, "", err return -1, "", err
} }
// Cache store // Cache store
if !volume.plainTextNames { if !volume.plainTextNames {
// TODO: openBackingDir already calls ReadDirIVAt(). Avoid duplicate work? var err error
iv, err := volume.nameTransform.ReadDirIVAt(dirfd) iv, err = volume.nameTransform.ReadDirIVAt(dirfd)
if err != nil { if err != nil {
syscall.Close(dirfd) syscall.Close(dirfd)
return -1, "", err 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 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 // decryptSymlinkTarget: "cData64" is base64-decoded and decrypted
// like file contents (GCM). // like file contents (GCM).
// The empty string decrypts to the empty string. // The empty string decrypts to the empty string.

View File

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