diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/fusefrontend/fs.go | 195 | ||||
| -rw-r--r-- | internal/fusefrontend/fs_dir.go | 184 | ||||
| -rw-r--r-- | internal/nametransform/longnames.go | 78 | ||||
| -rw-r--r-- | internal/nametransform/longnames_test.go | 6 | ||||
| -rw-r--r-- | internal/nametransform/names_diriv.go | 58 | 
5 files changed, 349 insertions, 172 deletions
| diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index fb82c6b..9e67a6a 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -5,6 +5,7 @@ package fusefrontend  import (  	"encoding/base64"  	"os" +	"path/filepath"  	"sync"  	"syscall"  	"time" @@ -111,18 +112,38 @@ func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Conte  	if err != nil {  		return nil, fuse.ToStatus(err)  	} -	// Create .name file to store the long file name if needed -	if !fs.args.PlaintextNames { -		err = fs.nameTransform.WriteLongName(cPath, path) + +	// Handle long file name +	cName := filepath.Base(cPath) +	if nametransform.IsLongContent(cName) { +		dirfd, err := os.Open(filepath.Dir(cPath)) +		if err != nil { +			return nil, fuse.ToStatus(err) +		} +		defer dirfd.Close() + +		// Create ".name" +		err = fs.nameTransform.WriteLongName(dirfd, cName, path) +		if err != nil { +			return nil, fuse.ToStatus(err) +		} + +		// Create content +		fdRaw, err := syscall.Openat(int(dirfd.Fd()), cName, iflags|os.O_CREATE, mode)  		if err != nil { +			nametransform.DeleteLongName(dirfd, cName)  			return nil, fuse.ToStatus(err)  		} +		fd := os.NewFile(uintptr(fdRaw), cName) + +		return NewFile(fd, writeOnly, fs.contentEnc), fuse.OK  	} -	f, err := os.OpenFile(cPath, iflags|os.O_CREATE, os.FileMode(mode)) + +	fd, err := os.OpenFile(cPath, iflags|os.O_CREATE, os.FileMode(mode))  	if err != nil {  		return nil, fuse.ToStatus(err)  	} -	return NewFile(f, writeOnly, fs.contentEnc), fuse.OK +	return NewFile(fd, writeOnly, fs.contentEnc), fuse.OK  }  func (fs *FS) Chmod(path string, mode uint32, context *fuse.Context) (code fuse.Status) { @@ -155,13 +176,31 @@ func (fs *FS) Mknod(path string, mode uint32, dev uint32, context *fuse.Context)  	if err != nil {  		return fuse.ToStatus(err)  	} -	if !fs.args.PlaintextNames { -		// Create .name file to store the long file name if needed -		err = fs.nameTransform.WriteLongName(cPath, path) + +	// Handle long file name +	cName := filepath.Base(cPath) +	if nametransform.IsLongContent(cName) { +		dirfd, err := os.Open(filepath.Dir(cPath))  		if err != nil {  			return fuse.ToStatus(err)  		} +		defer dirfd.Close() + +		// Create ".name" +		err = fs.nameTransform.WriteLongName(dirfd, cName, path) +		if err != nil { +			return fuse.ToStatus(err) +		} + +		// Create device node +		err = syscall.Mknodat(int(dirfd.Fd()), cName, uint32(mode), int(dev)) +		if err != nil { +			nametransform.DeleteLongName(dirfd, cName) +		} + +		return fuse.ToStatus(err)  	} +  	return fs.FileSystem.Mknod(cPath, mode, dev, context)  } @@ -227,11 +266,28 @@ func (fs *FS) Unlink(path string, context *fuse.Context) (code fuse.Status) {  	if err != nil {  		return fuse.ToStatus(err)  	} -	err = syscall.Unlink(cPath) -	// Delete .name file -	if err == nil && !fs.args.PlaintextNames { -		nametransform.DeleteLongName(cPath) + +	cName := filepath.Base(cPath) +	if nametransform.IsLongContent(cName) { +		dirfd, err := os.Open(filepath.Dir(cPath)) +		if err != nil { +			return fuse.ToStatus(err) +		} +		defer dirfd.Close() +		// Delete content +		err = syscall.Unlinkat(int(dirfd.Fd()), cName) +		if err != nil { +			return fuse.ToStatus(err) +		} +		// Delete ".name" +		err = nametransform.DeleteLongName(dirfd, cName) +		if err != nil { +			toggledlog.Warn.Printf("Unlink: could not delete .name file: %v", err) +		} +		return fuse.ToStatus(err)  	} + +	err = syscall.Unlink(cPath)  	return fuse.ToStatus(err)  } @@ -244,8 +300,8 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co  	if err != nil {  		return fuse.ToStatus(err)  	} -	// Old filesystem: symlinks are encrypted like paths (CBC) -	// TODO drop compatibility and simplify code +	// Before v0.5, symlinks were encrypted like paths (CBC) +	// TODO drop compatibility and simplify code?  	if !fs.args.DirIV {  		cTarget, err := fs.encryptPath(target)  		if err != nil { @@ -255,18 +311,36 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co  		err = os.Symlink(cTarget, cPath)  		return fuse.ToStatus(err)  	} -	// Since gocryptfs v0.5 symlinks are encrypted like file contents (GCM) +  	cBinTarget := fs.contentEnc.EncryptBlock([]byte(target), 0, nil)  	cTarget := base64.URLEncoding.EncodeToString(cBinTarget) -	if !fs.args.PlaintextNames { -		// Create .name file to store the long file name if needed -		err = fs.nameTransform.WriteLongName(cPath, linkName) + +	// Handle long file name +	cName := filepath.Base(cPath) +	if nametransform.IsLongContent(cName) { +		dirfd, err := os.Open(filepath.Dir(cPath))  		if err != nil {  			return fuse.ToStatus(err)  		} +		defer dirfd.Close() + +		// Create ".name" +		err = fs.nameTransform.WriteLongName(dirfd, cName, linkName) +		if err != nil { +			return fuse.ToStatus(err) +		} + +		// Create symlink +		// TODO use syscall.Symlinkat once it is available in Go +		err = syscall.Symlink(cTarget, cPath) +		if err != nil { +			nametransform.DeleteLongName(dirfd, cName) +		} + +		return fuse.ToStatus(err)  	} +  	err = os.Symlink(cTarget, cPath) -	toggledlog.Debug.Printf("Symlink: os.Symlink(%s, %s) = %v", cTarget, cPath, err)  	return fuse.ToStatus(err)  } @@ -286,35 +360,61 @@ func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (cod  	// That directory may still be in the DirIV cache, clear it.  	fs.nameTransform.DirIVCache.Clear() -	if !fs.args.PlaintextNames { -		// Create .name file to store the new long file name if needed -		err = fs.nameTransform.WriteLongName(cNewPath, newPath) +	// Handle long source file name +	var oldDirFd *os.File +	var finalOldDirFd int +	var finalOldPath = cOldPath +	cOldName := filepath.Base(cOldPath) +	if nametransform.IsLongContent(cOldName) { +		oldDirFd, err = os.Open(filepath.Dir(cOldPath)) +		if err != nil { +			return fuse.ToStatus(err) +		} +		defer oldDirFd.Close() +		finalOldDirFd = int(oldDirFd.Fd()) +		finalOldPath = cOldName +	} +	// Handle long destination file name +	var newDirFd *os.File +	var finalNewDirFd int +	var finalNewPath = cNewPath +	cNewName := filepath.Base(cNewPath) +	if nametransform.IsLongContent(cNewName) { +		newDirFd, err = os.Open(filepath.Dir(cNewPath)) +		if err != nil { +			return fuse.ToStatus(err) +		} +		defer newDirFd.Close() +		finalNewDirFd = int(newDirFd.Fd()) +		finalNewPath = cNewName +		// Create destination .name file +		err = fs.nameTransform.WriteLongName(newDirFd, cNewName, newPath)  		if err != nil {  			return fuse.ToStatus(err)  		}  	} -  	// Actual rename -	err = os.Rename(cOldPath, cNewPath) - -	if lerr, ok := err.(*os.LinkError); ok && lerr.Err == syscall.ENOTEMPTY { -		// If an empty directory is overwritten we will always get -		// ENOTEMPTY as the "empty" directory will still contain gocryptfs.diriv. +	err = syscall.Renameat(finalOldDirFd, finalOldPath, finalNewDirFd, finalNewPath) +	if err == syscall.ENOTEMPTY { +		// If an empty directory is overwritten we will always get ENOTEMPTY as +		// the "empty" directory will still contain gocryptfs.diriv.  		// Handle that case by removing the target directory and trying again.  		toggledlog.Debug.Printf("Rename: Handling ENOTEMPTY")  		if fs.Rmdir(newPath, context) == fuse.OK { -			err = os.Rename(cOldPath, cNewPath) +			err = syscall.Renameat(finalOldDirFd, finalOldPath, finalNewDirFd, finalNewPath)  		}  	} -	if err == nil { -		// Rename succeeded - delete old long name file -		nametransform.DeleteLongName(cOldPath) -	} else { -		// Rename has failed - undo long name file creation -		nametransform.DeleteLongName(cNewPath) +	if err != nil { +		if newDirFd != nil { +			// Roll back .name creation +			nametransform.DeleteLongName(newDirFd, cNewName) +		} +		return fuse.ToStatus(err)  	} - -	return fuse.ToStatus(err) +	if oldDirFd != nil { +		nametransform.DeleteLongName(oldDirFd, cOldName) +	} +	return fuse.OK  }  func (fs *FS) Link(oldPath string, newPath string, context *fuse.Context) (code fuse.Status) { @@ -329,13 +429,28 @@ func (fs *FS) Link(oldPath string, newPath string, context *fuse.Context) (code  	if err != nil {  		return fuse.ToStatus(err)  	} -	if !fs.args.PlaintextNames { -		// Create .name file to store the long file name if needed -		err = fs.nameTransform.WriteLongName(cNewPath, newPath) + +	// Handle long file name +	cNewName := filepath.Base(cNewPath) +	if nametransform.IsLongContent(cNewName) { +		dirfd, err := os.Open(filepath.Dir(cNewPath)) +		if err != nil { +			return fuse.ToStatus(err) +		} +		defer dirfd.Close() +		err = fs.nameTransform.WriteLongName(dirfd, cNewName, newPath)  		if err != nil {  			return fuse.ToStatus(err)  		} +		// TODO Use syscall.Linkat once it is available in Go (it is not in Go +		// 1.6). +		err = syscall.Link(cOldPath, cNewPath) +		if err != nil { +			nametransform.DeleteLongName(dirfd, cNewName) +			return fuse.ToStatus(err) +		}  	} +  	return fuse.ToStatus(os.Link(cOldPath, cNewPath))  } diff --git a/internal/fusefrontend/fs_dir.go b/internal/fusefrontend/fs_dir.go index bcb93af..41e2101 100644 --- a/internal/fusefrontend/fs_dir.go +++ b/internal/fusefrontend/fs_dir.go @@ -16,50 +16,74 @@ import (  	"github.com/rfjakob/gocryptfs/internal/toggledlog"  ) -func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fuse.Status) { -	if fs.isFiltered(relPath) { +func (fs *FS) mkdirWithIv(cPath string, mode uint32) error { +	// Between the creation of the directory and the creation of gocryptfs.diriv +	// the directory is inconsistent. Take the lock to prevent other readers. +	fs.dirIVLock.Lock() +	// The new directory may take the place of an older one that is still in the cache +	fs.nameTransform.DirIVCache.Clear() +	defer fs.dirIVLock.Unlock() +	err := os.Mkdir(cPath, os.FileMode(mode)) +	if err != nil { +		return err +	} +	// Create gocryptfs.diriv +	err = nametransform.WriteDirIV(cPath) +	if err != nil { +		err2 := syscall.Rmdir(cPath) +		if err2 != nil { +			toggledlog.Warn.Printf("mkdirWithIv: rollback failed: %v", err2) +		} +	} +	return err +} + +func (fs *FS) Mkdir(newPath string, mode uint32, context *fuse.Context) (code fuse.Status) { +	if fs.isFiltered(newPath) {  		return fuse.EPERM  	} -	encPath, err := fs.getBackingPath(relPath) +	cPath, err := fs.getBackingPath(newPath)  	if err != nil {  		return fuse.ToStatus(err)  	}  	if !fs.args.DirIV { -		return fuse.ToStatus(os.Mkdir(encPath, os.FileMode(mode))) +		return fuse.ToStatus(os.Mkdir(cPath, os.FileMode(mode)))  	} -  	// We need write and execute permissions to create gocryptfs.diriv  	origMode := mode  	mode = mode | 0300 -	// Create .name file to store the long file name if needed -	err = fs.nameTransform.WriteLongName(encPath, relPath) -	if err != nil { -		return fuse.ToStatus(err) -	} -	// The new directory may take the place of an older one that is still in the cache -	fs.nameTransform.DirIVCache.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 = nametransform.WriteDirIV(encPath) -	if err != nil { -		// This should not happen -		toggledlog.Warn.Printf("Mkdir: WriteDirIV failed: %v", err) -		err2 := syscall.Rmdir(encPath) -		if err2 != nil { -			toggledlog.Warn.Printf("Mkdir: Rmdir rollback failed: %v", err2) + +	// Handle long file name +	cName := filepath.Base(cPath) +	if nametransform.IsLongContent(cName) { +		dirfd, err := os.Open(filepath.Dir(cPath)) +		if err != nil { +			return fuse.ToStatus(err) +		} +		defer dirfd.Close() + +		// Create ".name" +		err = fs.nameTransform.WriteLongName(dirfd, cName, newPath) +		if err != nil { +			return fuse.ToStatus(err) +		} + +		// Create directory +		err = fs.mkdirWithIv(cPath, mode) +		if err != nil { +			nametransform.DeleteLongName(dirfd, cName) +			return fuse.ToStatus(err) +		} +	} else { +		err = fs.mkdirWithIv(cPath, mode) +		if err != nil { +			return fuse.ToStatus(err)  		} -		return fuse.ToStatus(err)  	}  	// Set permissions back to what the user wanted  	if origMode != mode { -		err = os.Chmod(encPath, os.FileMode(origMode)) +		err = os.Chmod(cPath, os.FileMode(origMode))  		if err != nil {  			toggledlog.Warn.Printf("Mkdir: Chmod failed: %v", err)  		} @@ -68,97 +92,111 @@ func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fu  	return fuse.OK  } -func (fs *FS) Rmdir(name string, context *fuse.Context) (code fuse.Status) { -	encPath, err := fs.getBackingPath(name) +func (fs *FS) Rmdir(path string, context *fuse.Context) (code fuse.Status) { +	cPath, err := fs.getBackingPath(path)  	if err != nil {  		return fuse.ToStatus(err)  	}  	if !fs.args.DirIV { -		return fuse.ToStatus(syscall.Rmdir(encPath)) +		return fuse.ToStatus(syscall.Rmdir(cPath))  	} -	// 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 { +	parentDir := filepath.Dir(cPath) +	parentDirFd, err := os.Open(parentDir) +	if err != nil { +		return fuse.ToStatus(err) +	} +	defer parentDirFd.Close() + +	cName := filepath.Base(cPath) +	dirfdRaw, err := syscall.Openat(int(parentDirFd.Fd()), cName, +		syscall.O_RDONLY, 0) +	if err == syscall.EACCES {  		// We need permission to read and modify the directory  		toggledlog.Debug.Printf("Rmdir: handling EACCESS") -		fi, err2 := os.Stat(encPath) -		if err2 != nil { -			toggledlog.Debug.Printf("Rmdir: Stat: %v", err2) -			return fuse.ToStatus(err2) +		// TODO use syscall.Fstatat once it is available in Go +		var fi os.FileInfo +		fi, err = os.Lstat(cPath) +		if err != nil { +			toggledlog.Debug.Printf("Rmdir: Stat: %v", err) +			return fuse.ToStatus(err)  		}  		origMode := fi.Mode() -		newMode := origMode | 0700 -		err2 = os.Chmod(encPath, newMode) -		if err2 != nil { -			toggledlog.Debug.Printf("Rmdir: Chmod failed: %v", err2) +		// TODO use syscall.Chmodat once it is available in Go +		err = os.Chmod(cPath, origMode|0700) +		if err != nil { +			toggledlog.Debug.Printf("Rmdir: Chmod failed: %v", err)  			return fuse.ToStatus(err)  		} +		// Retry open +		var st syscall.Stat_t +		syscall.Lstat(cPath, &st) +		dirfdRaw, err = syscall.Openat(int(parentDirFd.Fd()), cName, +			syscall.O_RDONLY, 0) +		// Undo the chmod if removing the directory failed  		defer func() {  			if code != fuse.OK { -				// Undo the chmod if removing the directory failed -				err3 := os.Chmod(encPath, origMode) -				if err3 != nil { -					toggledlog.Warn.Printf("Rmdir: Chmod rollback failed: %v", err2) +				err := os.Chmod(cPath, origMode) +				if err != nil { +					toggledlog.Warn.Printf("Rmdir: Chmod rollback failed: %v", err)  				}  			}  		}() -		// Retry open -		fd, err = os.Open(encPath)  	}  	if err != nil {  		toggledlog.Debug.Printf("Rmdir: Open: %v", err)  		return fuse.ToStatus(err)  	} -	list, err := fd.Readdirnames(10) -	fd.Close() +	dirfd := os.NewFile(uintptr(dirfdRaw), cName) +	defer dirfd.Close() + +	children, err := dirfd.Readdirnames(10)  	if err != nil { -		toggledlog.Debug.Printf("Rmdir: Readdirnames: %v", err) +		toggledlog.Warn.Printf("Rmdir: Readdirnames: %v", err)  		return fuse.ToStatus(err)  	} -	if len(list) > 1 { +	// If the directory is not empty besides gocryptfs.diriv, do not even +	// attempt the dance around gocryptfs.diriv. +	if len(children) > 1 {  		return fuse.ToStatus(syscall.ENOTEMPTY) -	} else if len(list) == 0 { -		toggledlog.Warn.Printf("Rmdir: gocryptfs.diriv missing, allowing deletion") -		return fuse.ToStatus(syscall.Rmdir(encPath))  	}  	// Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ" -	dirivPath := filepath.Join(encPath, nametransform.DirIVFilename) -	parentDir := filepath.Dir(encPath)  	tmpName := fmt.Sprintf("gocryptfs.diriv.rmdir.%d", cryptocore.RandUint64()) -	tmpDirivPath := filepath.Join(parentDir, tmpName) -	toggledlog.Debug.Printf("Rmdir: Renaming %s to %s", nametransform.DirIVFilename, tmpDirivPath) -	// The directory is in an inconsistent state between rename and rmdir. Protect against -	// concurrent readers. +	toggledlog.Debug.Printf("Rmdir: Renaming %s to %s", nametransform.DirIVFilename, tmpName) +	// 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) +	err = syscall.Renameat(int(dirfd.Fd()), nametransform.DirIVFilename, +		int(parentDirFd.Fd()), tmpName)  	if err != nil {  		toggledlog.Warn.Printf("Rmdir: Renaming %s to %s failed: %v", -			nametransform.DirIVFilename, tmpDirivPath, err) +			nametransform.DirIVFilename, tmpName, err)  		return fuse.ToStatus(err)  	}  	// Actual Rmdir -	err = syscall.Rmdir(encPath) +	// TODO Use syscall.Unlinkat with the AT_REMOVEDIR flag once it is available +	// in Go +	err = syscall.Rmdir(cPath)  	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 { +		err2 := syscall.Renameat(int(parentDirFd.Fd()), tmpName, +			int(dirfd.Fd()), nametransform.DirIVFilename) +		if err != nil {  			toggledlog.Warn.Printf("Rmdir: Rename rollback failed: %v", err2)  		}  		return fuse.ToStatus(err)  	}  	// Delete "gocryptfs.diriv.rmdir.INODENUMBER" -	err = syscall.Unlink(tmpDirivPath) +	err = syscall.Unlinkat(int(parentDirFd.Fd()), tmpName)  	if err != nil {  		toggledlog.Warn.Printf("Rmdir: Could not clean up %s: %v", tmpName, err)  	} -	err = nametransform.DeleteLongName(encPath) -	if err != nil { -		toggledlog.Warn.Printf("Rmdir: Could not delete long name file: %v", err) +	// Delete .name file +	if nametransform.IsLongContent(cName) { +		nametransform.DeleteLongName(parentDirFd, cName)  	}  	// The now-deleted directory may have been in the DirIV cache. Clear it.  	fs.nameTransform.DirIVCache.Clear() @@ -206,7 +244,7 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f  		}  		if fs.args.LongNames { -			isLong := nametransform.IsLongName(cName) +			isLong := nametransform.NameType(cName)  			if isLong == nametransform.LongNameContent {  				cNameLong, err := nametransform.ReadLongName(filepath.Join(cDirAbsPath, cName))  				if err != nil { diff --git a/internal/nametransform/longnames.go b/internal/nametransform/longnames.go index d048f95..0746cd6 100644 --- a/internal/nametransform/longnames.go +++ b/internal/nametransform/longnames.go @@ -4,6 +4,7 @@ import (  	"crypto/sha256"  	"encoding/base64"  	"io/ioutil" +	"os"  	"path/filepath"  	"strings"  	"syscall" @@ -11,11 +12,13 @@ import (  	"github.com/rfjakob/gocryptfs/internal/toggledlog"  ) -// Files with long names are stored in two files: -// gocryptfs.longname.[sha256]       <--- File content -// gocryptfs.longname.[sha256].name  <--- File name -const longNamePrefix = "gocryptfs.longname." -const longNameSuffix = ".name" +const ( +	// Files with long names are stored in two files: +	// gocryptfs.longname.[sha256]       <--- File content, prefix = gocryptfs.longname. +	// gocryptfs.longname.[sha256].name  <--- File name, suffix = .name +	LongNameSuffix = ".name" +	longNamePrefix = "gocryptfs.longname." +)  // HashLongName - take the hash of a long string "name" and return  // "gocryptfs.longname.[sha256]" @@ -32,63 +35,68 @@ const (  	LongNameNone     = iota  ) -// IsLongName - detect if cName is +// NameType - detect if cName is  // gocryptfs.longname.[sha256]  ........ LongNameContent (content of a long name file)  // gocryptfs.longname.[sha256].name .... LongNameFilename (full file name of a long name file)  // else ................................ LongNameNone (normal file) -func IsLongName(cName string) int { +func NameType(cName string) int {  	if !strings.HasPrefix(cName, longNamePrefix) {  		return LongNameNone  	} -	if strings.HasSuffix(cName, longNameSuffix) { +	if strings.HasSuffix(cName, LongNameSuffix) {  		return LongNameFilename  	}  	return LongNameContent  } +// IsLongContent returns true if "cName" is the content store of a long name file. +func IsLongContent(cName string) bool { +	return NameType(cName) == LongNameContent +} +  // ReadLongName - read path.name  func ReadLongName(path string) (string, error) { -	content, err := ioutil.ReadFile(path + longNameSuffix) +	content, err := ioutil.ReadFile(path + LongNameSuffix)  	if err != nil {  		toggledlog.Warn.Printf("ReadLongName: %v", err)  	}  	return string(content), err  } -// DeleteLongName - if cPath ends in "gocryptfs.longname.[sha256]", -// delete the "gocryptfs.longname.[sha256].name" file -func DeleteLongName(cPath string) error { -	if IsLongName(filepath.Base(cPath)) == LongNameContent { -		err := syscall.Unlink(cPath + longNameSuffix) -		if err != nil { -			toggledlog.Warn.Printf("DeleteLongName: %v", err) -		} -		return err +// DeleteLongName deletes "hashName.name". +func DeleteLongName(dirfd *os.File, hashName string) error { +	err := syscall.Unlinkat(int(dirfd.Fd()), hashName+LongNameSuffix) +	if err != nil { +		toggledlog.Warn.Printf("DeleteLongName: %v", err)  	} -	return nil +	return err  } -// WriteLongName - if cPath ends in "gocryptfs.longname.[sha256]", write the -// "gocryptfs.longname.[sha256].name" file -func (n *NameTransform) WriteLongName(cPath string, plainPath string) (err error) { -	cHashedName := filepath.Base(cPath) -	if IsLongName(cHashedName) != LongNameContent { -		// This is not a hashed file name, nothing to do -		return nil -	} -	// Encrypt (but do not hash) the plaintext name -	cDir := filepath.Dir(cPath) -	dirIV, err := ReadDirIV(cDir) +// WriteLongName encrypts plainName and writes it into "hashName.name". +// For the convenience of the caller, plainName may also be a path and will be +// converted internally. +func (n *NameTransform) WriteLongName(dirfd *os.File, hashName string, plainName string) (err error) { +	plainName = filepath.Base(plainName) + +	// Encrypt the basename +	dirIV, err := ReadDirIVAt(dirfd)  	if err != nil { -		toggledlog.Warn.Printf("WriteLongName: %v", err)  		return err  	} -	plainName := filepath.Base(plainPath)  	cName := n.EncryptName(plainName, dirIV) -	// Write the encrypted name into gocryptfs.longname.[sha256].name -	err = ioutil.WriteFile(filepath.Join(cDir, cHashedName+longNameSuffix), []byte(cName), 0600) + +	// Write the encrypted name into hashName.name +	fdRaw, err := syscall.Openat(int(dirfd.Fd()), hashName+LongNameSuffix, +		syscall.O_WRONLY|syscall.O_CREAT|syscall.O_EXCL, 0600) +	if err != nil { +		toggledlog.Warn.Printf("WriteLongName: Openat: %v", err) +		return err +	} +	fd := os.NewFile(uintptr(fdRaw), hashName+LongNameSuffix) +	defer fd.Close() +	_, err = fd.Write([]byte(cName))  	if err != nil { -		toggledlog.Warn.Printf("WriteLongName: %v", err) +		toggledlog.Warn.Printf("WriteLongName: Write: %v", err)  	}  	return err  } diff --git a/internal/nametransform/longnames_test.go b/internal/nametransform/longnames_test.go index 62073ec..8fa19fe 100644 --- a/internal/nametransform/longnames_test.go +++ b/internal/nametransform/longnames_test.go @@ -6,17 +6,17 @@ import (  func TestIsLongName(t *testing.T) {  	n := "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=.name" -	if IsLongName(n) != LongNameFilename { +	if NameType(n) != LongNameFilename {  		t.Errorf("False negative")  	}  	n = "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=" -	if IsLongName(n) != LongNameContent { +	if NameType(n) != LongNameContent {  		t.Errorf("False negative")  	}  	n = "LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=" -	if IsLongName(n) != LongNameNone { +	if NameType(n) != LongNameNone {  		t.Errorf("False positive")  	}  } diff --git a/internal/nametransform/names_diriv.go b/internal/nametransform/names_diriv.go index 9336f5d..1beda3f 100644 --- a/internal/nametransform/names_diriv.go +++ b/internal/nametransform/names_diriv.go @@ -1,7 +1,7 @@  package nametransform  import ( -	"fmt" +	"errors"  	"io/ioutil"  	"os"  	"path/filepath" @@ -21,25 +21,38 @@ const (  )  // ReadDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path) -// This function is exported because it allows for an efficient readdir implementation -func ReadDirIV(dir string) (iv []byte, readErr error) { -	ivfile := filepath.Join(dir, DirIVFilename) -	toggledlog.Debug.Printf("ReadDirIV: reading %s\n", ivfile) -	iv, readErr = ioutil.ReadFile(ivfile) -	if readErr != nil { -		// The directory may have been concurrently deleted or moved. Failure to -		// read the diriv is not an error in that case. -		_, statErr := os.Stat(dir) -		if os.IsNotExist(statErr) { -			toggledlog.Debug.Printf("ReadDirIV: Dir %s was deleted under our feet", dir) -		} else { -			// This should not happen -			toggledlog.Warn.Printf("ReadDirIV: Dir exists but diriv does not: %v\n", readErr) -		} -		return nil, readErr +// This function is exported because it allows for an efficient readdir implementation. +func ReadDirIV(dir string) (iv []byte, err error) { +	dirfd, err := os.Open(dir) +	if err != nil { +		return nil, err +	} +	defer dirfd.Close() + +	return ReadDirIVAt(dirfd) +} + +// ReadDirIVAt reads "gocryptfs.diriv" from the directory that is opened as "dirfd". +// Using the dirfd makes it immune to concurrent renames of the directory. +func ReadDirIVAt(dirfd *os.File) (iv []byte, err error) { +	fdRaw, err := syscall.Openat(int(dirfd.Fd()), DirIVFilename, syscall.O_RDONLY, 0) +	if err != nil { +		toggledlog.Warn.Printf("ReadDirIVAt: %v", err) +		return nil, err +	} +	fd := os.NewFile(uintptr(fdRaw), DirIVFilename) +	defer fd.Close() + +	iv = make([]byte, dirIVLen+1) +	n, err := fd.Read(iv) +	if err != nil { +		toggledlog.Warn.Printf("ReadDirIVAt: %v", err) +		return nil, err  	} +	iv = iv[0:n]  	if len(iv) != dirIVLen { -		return nil, fmt.Errorf("ReadDirIV: Invalid length %d\n", len(iv)) +		toggledlog.Warn.Printf("ReadDirIVAt: wanted %d bytes, got %d", dirIVLen, len(iv)) +		return nil, errors.New("invalid iv length")  	}  	return iv, nil  } @@ -50,12 +63,15 @@ func ReadDirIV(dir string) (iv []byte, readErr error) {  func WriteDirIV(dir string) error {  	iv := cryptocore.RandBytes(dirIVLen)  	file := filepath.Join(dir, DirIVFilename) -	// 0444 permissions: the file is not secret but should not be written to -	return ioutil.WriteFile(file, iv, 0444) +	err := ioutil.WriteFile(file, iv, 0400) +	if err != nil { +		toggledlog.Warn.Printf("WriteDirIV: %v", err) +	} +	return err  }  // EncryptPathDirIV - encrypt relative plaintext path using EME with DirIV. -// Components that are longer than 255 bytes are hashed. +// Components that are longer than 255 bytes are hashed if be.longnames == true.  func (be *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (cipherPath string, err error) {  	// Empty string means root directory  	if plainPath == "" { | 
