Rmdir: handle creating and removing unreadable directories
This patch also splits off Mkdir and Rmdir into its own file. Fixes issue #8, thanks to @diseq for the bug report.
This commit is contained in:
parent
78cd97c5e9
commit
e99e841713
@ -1,6 +1,7 @@
|
||||
package cryptfs
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
@ -17,6 +18,12 @@ func RandBytes(n int) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
// Return a secure random uint64
|
||||
func RandUint64() uint64 {
|
||||
b := RandBytes(8)
|
||||
return binary.BigEndian.Uint64(b)
|
||||
}
|
||||
|
||||
var gcmNonce nonce96
|
||||
|
||||
type nonce96 struct {
|
||||
|
@ -127,6 +127,38 @@ func testMkdirRmdir(t *testing.T, plainDir string) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Removing a non-empty dir should fail with ENOTEMPTY
|
||||
if os.Mkdir(dir, 0777) != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := os.Create(dir + "/file")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
err = syscall.Rmdir(dir)
|
||||
errno := err.(syscall.Errno)
|
||||
if errno != syscall.ENOTEMPTY {
|
||||
t.Errorf("Should have gotten ENOTEMPTY, go %v", errno)
|
||||
}
|
||||
if syscall.Unlink(dir + "/file") != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if syscall.Rmdir(dir) != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// We should also be able to remove a directory we do not have permissions to
|
||||
// read or write
|
||||
err = os.Mkdir(dir, 0000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = syscall.Rmdir(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create and rename a file
|
||||
|
7
main.go
7
main.go
@ -159,7 +159,7 @@ func main() {
|
||||
"Setting this to a lower value speeds up mounting but makes the password susceptible to brute-force attacks")
|
||||
flagSet.Parse(os.Args[1:])
|
||||
|
||||
// Fork a child into the background if "-f" is not set and we are mounting a filesystem
|
||||
// Fork a child into the background if "-f" is not set AND we are mounting a filesystem
|
||||
if !args.foreground && flagSet.NArg() == 2 {
|
||||
forkChild() // does not return
|
||||
}
|
||||
@ -339,6 +339,11 @@ func pathfsFrontend(key []byte, args argContainer, confFile *cryptfs.ConfFile) *
|
||||
}
|
||||
srv.SetDebug(args.fusedebug)
|
||||
|
||||
// All FUSE file and directory create calls carry explicit permission
|
||||
// information. We need an unrestricted umask to create the files and
|
||||
// directories with the requested permissions.
|
||||
syscall.Umask(0000)
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package pathfs_frontend
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
@ -245,40 +244,7 @@ func (fs *FS) Readlink(path string, context *fuse.Context) (out string, status f
|
||||
return string(target), fuse.OK
|
||||
}
|
||||
|
||||
func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fuse.Status) {
|
||||
if fs.isFiltered(relPath) {
|
||||
return fuse.EPERM
|
||||
}
|
||||
encPath, err := fs.getBackingPath(relPath)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
if !fs.args.DirIV {
|
||||
return fuse.ToStatus(os.Mkdir(encPath, os.FileMode(mode)))
|
||||
}
|
||||
|
||||
// The new directory may take the place of an older one that is still in the cache
|
||||
fs.CryptFS.DirIVCacheEnc.Clear()
|
||||
// Create directory
|
||||
fs.dirIVLock.Lock()
|
||||
defer fs.dirIVLock.Unlock()
|
||||
err = os.Mkdir(encPath, os.FileMode(mode))
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Create gocryptfs.diriv inside
|
||||
err = cryptfs.WriteDirIV(encPath)
|
||||
if err != nil {
|
||||
// This should not happen
|
||||
cryptfs.Warn.Printf("Creating %s in dir %s failed: %v\n", cryptfs.DIRIV_FILENAME, encPath, err)
|
||||
err2 := syscall.Rmdir(encPath)
|
||||
if err2 != nil {
|
||||
cryptfs.Warn.Printf("Mkdir: Rollback failed: %v\n", err2)
|
||||
}
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
func (fs *FS) Unlink(path string, context *fuse.Context) (code fuse.Status) {
|
||||
if fs.isFiltered(path) {
|
||||
@ -291,71 +257,6 @@ func (fs *FS) Unlink(path string, context *fuse.Context) (code fuse.Status) {
|
||||
return fuse.ToStatus(syscall.Unlink(cPath))
|
||||
}
|
||||
|
||||
func (fs *FS) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
|
||||
encPath, err := fs.getBackingPath(name)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
if !fs.args.DirIV {
|
||||
return fuse.ToStatus(syscall.Rmdir(encPath))
|
||||
}
|
||||
|
||||
// If the directory is not empty besides gocryptfs.diriv, do not even
|
||||
// attempt the dance around gocryptfs.diriv.
|
||||
fd, err := os.Open(encPath)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer fd.Close()
|
||||
list, err := fd.Readdirnames(10)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
if len(list) > 1 {
|
||||
return fuse.ToStatus(syscall.ENOTEMPTY)
|
||||
}
|
||||
|
||||
// Move "gocryptfs.diriv" to the parent dir under name "gocryptfs.diriv.rmdir.INODENUMBER"
|
||||
var st syscall.Stat_t
|
||||
err = syscall.Fstat(int(fd.Fd()), &st)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
dirivPath := filepath.Join(encPath, cryptfs.DIRIV_FILENAME)
|
||||
parentDir := filepath.Dir(encPath)
|
||||
tmpName := fmt.Sprintf("gocryptfs.diriv.rmdir.%d", st.Ino)
|
||||
tmpDirivPath := filepath.Join(parentDir, tmpName)
|
||||
cryptfs.Debug.Printf("Rmdir: Renaming %s to %s\n", cryptfs.DIRIV_FILENAME, tmpDirivPath)
|
||||
// The directory is in an inconsistent state between rename and rmdir. Protect against
|
||||
// concurrent readers.
|
||||
fs.dirIVLock.Lock()
|
||||
defer fs.dirIVLock.Unlock()
|
||||
err = os.Rename(dirivPath, tmpDirivPath)
|
||||
if err != nil {
|
||||
cryptfs.Warn.Printf("Rmdir: Renaming %s to %s failed: %v\n", cryptfs.DIRIV_FILENAME, tmpDirivPath, err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Actual Rmdir
|
||||
err = syscall.Rmdir(encPath)
|
||||
if err != nil {
|
||||
// This can happen if another file in the directory was created in the
|
||||
// meantime, undo the rename
|
||||
err2 := os.Rename(tmpDirivPath, dirivPath)
|
||||
if err2 != nil {
|
||||
cryptfs.Warn.Printf("Rmdir: Rollback failed: %v\n", err2)
|
||||
}
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Delete "gocryptfs.diriv.rmdir.INODENUMBER"
|
||||
err = syscall.Unlink(tmpDirivPath)
|
||||
if err != nil {
|
||||
cryptfs.Warn.Printf("Rmdir: Could not clean up %s: %v\n", tmpName, err)
|
||||
}
|
||||
// The now-deleted directory may have been in the DirIV cache. Clear it.
|
||||
fs.CryptFS.DirIVCacheEnc.Clear()
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (code fuse.Status) {
|
||||
cryptfs.Debug.Printf("Symlink(\"%s\", \"%s\")\n", target, linkName)
|
||||
if fs.isFiltered(linkName) {
|
||||
|
151
pathfs_frontend/fs_dir.go
Normal file
151
pathfs_frontend/fs_dir.go
Normal file
@ -0,0 +1,151 @@
|
||||
package pathfs_frontend
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"fmt"
|
||||
|
||||
"github.com/hanwen/go-fuse/fuse"
|
||||
"github.com/rfjakob/gocryptfs/cryptfs"
|
||||
)
|
||||
|
||||
func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fuse.Status) {
|
||||
if fs.isFiltered(relPath) {
|
||||
return fuse.EPERM
|
||||
}
|
||||
encPath, err := fs.getBackingPath(relPath)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
if !fs.args.DirIV {
|
||||
return fuse.ToStatus(os.Mkdir(encPath, os.FileMode(mode)))
|
||||
}
|
||||
|
||||
// We need write and execute permissions to create gocryptfs.diriv
|
||||
origMode := mode
|
||||
mode = mode | 0300
|
||||
|
||||
// The new directory may take the place of an older one that is still in the cache
|
||||
fs.CryptFS.DirIVCacheEnc.Clear()
|
||||
// Create directory
|
||||
fs.dirIVLock.Lock()
|
||||
defer fs.dirIVLock.Unlock()
|
||||
err = os.Mkdir(encPath, os.FileMode(mode))
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Create gocryptfs.diriv inside
|
||||
err = cryptfs.WriteDirIV(encPath)
|
||||
if err != nil {
|
||||
// This should not happen
|
||||
cryptfs.Warn.Printf("Mkdir: WriteDirIV failed: %v\n", err)
|
||||
err2 := syscall.Rmdir(encPath)
|
||||
if err2 != nil {
|
||||
cryptfs.Warn.Printf("Mkdir: Rmdir rollback failed: %v\n", err2)
|
||||
}
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
// Set permissions back to what the user wanted
|
||||
if origMode != mode {
|
||||
err = os.Chmod(encPath, os.FileMode(origMode))
|
||||
if err != nil {
|
||||
cryptfs.Warn.Printf("Mkdir: Chmod failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
func (fs *FS) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
|
||||
encPath, err := fs.getBackingPath(name)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
if !fs.args.DirIV {
|
||||
return fuse.ToStatus(syscall.Rmdir(encPath))
|
||||
}
|
||||
|
||||
// If the directory is not empty besides gocryptfs.diriv, do not even
|
||||
// attempt the dance around gocryptfs.diriv.
|
||||
fd, err := os.Open(encPath)
|
||||
if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.EACCES {
|
||||
// We need permission to read and modify the directory
|
||||
cryptfs.Debug.Printf("Rmdir: handling EACCESS\n")
|
||||
fi, err2 := os.Stat(encPath)
|
||||
if err2 != nil {
|
||||
cryptfs.Debug.Printf("Rmdir: Stat: %v\n", err2)
|
||||
return fuse.ToStatus(err2)
|
||||
}
|
||||
origMode := fi.Mode()
|
||||
newMode := origMode | 0700
|
||||
err2 = os.Chmod(encPath, newMode)
|
||||
if err2 != nil {
|
||||
cryptfs.Debug.Printf("Rmdir: Chmod failed: %v\n", err2)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer func () {
|
||||
if code != fuse.OK {
|
||||
// Undo the chmod if removing the directory failed
|
||||
err3 := os.Chmod(encPath, origMode)
|
||||
if err3 != nil {
|
||||
cryptfs.Warn.Printf("Rmdir: Chmod rollback failed: %v\n", err2)
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Retry open
|
||||
fd, err = os.Open(encPath)
|
||||
}
|
||||
if err != nil {
|
||||
cryptfs.Debug.Printf("Rmdir: Open: %v\n", err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
list, err := fd.Readdirnames(10)
|
||||
fd.Close()
|
||||
if err != nil {
|
||||
cryptfs.Debug.Printf("Rmdir: Readdirnames: %v\n", err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
if len(list) > 1 {
|
||||
return fuse.ToStatus(syscall.ENOTEMPTY)
|
||||
} else if len(list) == 0 {
|
||||
cryptfs.Warn.Printf("Rmdir: gocryptfs.diriv missing, allowing deletion\n")
|
||||
return fuse.ToStatus(syscall.Rmdir(encPath))
|
||||
}
|
||||
|
||||
// Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ"
|
||||
dirivPath := filepath.Join(encPath, cryptfs.DIRIV_FILENAME)
|
||||
parentDir := filepath.Dir(encPath)
|
||||
tmpName := fmt.Sprintf("gocryptfs.diriv.rmdir.%d", cryptfs.RandUint64())
|
||||
tmpDirivPath := filepath.Join(parentDir, tmpName)
|
||||
cryptfs.Debug.Printf("Rmdir: Renaming %s to %s\n", cryptfs.DIRIV_FILENAME, tmpDirivPath)
|
||||
// The directory is in an inconsistent state between rename and rmdir. Protect against
|
||||
// concurrent readers.
|
||||
fs.dirIVLock.Lock()
|
||||
defer fs.dirIVLock.Unlock()
|
||||
err = os.Rename(dirivPath, tmpDirivPath)
|
||||
if err != nil {
|
||||
cryptfs.Warn.Printf("Rmdir: Renaming %s to %s failed: %v\n", cryptfs.DIRIV_FILENAME, tmpDirivPath, err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Actual Rmdir
|
||||
err = syscall.Rmdir(encPath)
|
||||
if err != nil {
|
||||
// This can happen if another file in the directory was created in the
|
||||
// meantime, undo the rename
|
||||
err2 := os.Rename(tmpDirivPath, dirivPath)
|
||||
if err2 != nil {
|
||||
cryptfs.Warn.Printf("Rmdir: Rename rollback failed: %v\n", err2)
|
||||
}
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Delete "gocryptfs.diriv.rmdir.INODENUMBER"
|
||||
err = syscall.Unlink(tmpDirivPath)
|
||||
if err != nil {
|
||||
cryptfs.Warn.Printf("Rmdir: Could not clean up %s: %v\n", tmpName, err)
|
||||
}
|
||||
// The now-deleted directory may have been in the DirIV cache. Clear it.
|
||||
fs.CryptFS.DirIVCacheEnc.Clear()
|
||||
return fuse.OK
|
||||
}
|
Loading…
Reference in New Issue
Block a user