fusefrontend_reverse: secure Access against symlink races (somewhat)
Unfortunately, faccessat in Linux ignores AT_SYMLINK_NOFOLLOW, so this is not completely atomic. Given that the information you get from access is not very interesting, it seems good enough. https://github.com/rfjakob/gocryptfs/issues/165
This commit is contained in:
parent
2ceef01afe
commit
87736eb833
|
@ -201,11 +201,16 @@ func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context)
|
||||||
}
|
}
|
||||||
return fuse.EPERM
|
return fuse.EPERM
|
||||||
}
|
}
|
||||||
absPath, err := rfs.abs(rfs.decryptPath(relPath))
|
dirfd, name, err := rfs.openBackingDir(relPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fuse.ToStatus(err)
|
return fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
return fuse.ToStatus(syscall.Access(absPath, mode))
|
err = syscallcompat.Faccessat(dirfd, name, mode)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("name=%q err=%v", name, err)
|
||||||
|
}
|
||||||
|
syscall.Close(dirfd)
|
||||||
|
return fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open - FUSE call
|
// Open - FUSE call
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
||||||
"github.com/rfjakob/gocryptfs/internal/pathiv"
|
"github.com/rfjakob/gocryptfs/internal/pathiv"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -89,3 +90,23 @@ func (rfs *ReverseFS) decryptPath(relPath string) (string, error) {
|
||||||
rPathCache.store(cDir, dirIV, nametransform.Dir(pRelPath))
|
rPathCache.store(cDir, dirIV, nametransform.Dir(pRelPath))
|
||||||
return pRelPath, nil
|
return pRelPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// openBackingDir decrypt the relative ciphertext path "cRelPath", opens
|
||||||
|
// the directory that contains the target file/dir and returns the fd to
|
||||||
|
// the directory and the decrypted name of the target file.
|
||||||
|
// The fd/name pair is intended for use with fchownat and friends.
|
||||||
|
func (rfs *ReverseFS) openBackingDir(cRelPath string) (dirfd int, pName string, err error) {
|
||||||
|
// Decrypt relative path
|
||||||
|
pRelPath, err := rfs.decryptPath(cRelPath)
|
||||||
|
if err != nil {
|
||||||
|
return -1, "", err
|
||||||
|
}
|
||||||
|
// Open directory, safe against symlink races
|
||||||
|
pDir := filepath.Dir(pRelPath)
|
||||||
|
dirfd, err = syscallcompat.OpenNofollow(rfs.args.Cipherdir, pDir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return -1, "", err
|
||||||
|
}
|
||||||
|
pName = filepath.Base(pRelPath)
|
||||||
|
return dirfd, pName, nil
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,10 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/ctlsock"
|
"github.com/rfjakob/gocryptfs/internal/ctlsock"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||||
"github.com/rfjakob/gocryptfs/tests/test_helpers"
|
"github.com/rfjakob/gocryptfs/tests/test_helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -138,6 +141,35 @@ func TestAccessVirtual(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that the access() syscall works on regular files
|
||||||
|
func TestAccess(t *testing.T) {
|
||||||
|
f, err := os.Create(dirA + "/testaccess1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
f, err = os.Open(dirB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
names, err := f.Readdirnames(0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, n := range names {
|
||||||
|
// Check if file exists - this should never fail
|
||||||
|
err = syscallcompat.Faccessat(unix.AT_FDCWD, dirB+"/"+n, unix.F_OK)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", n, err)
|
||||||
|
}
|
||||||
|
// Check if file is readable
|
||||||
|
err = syscallcompat.Faccessat(unix.AT_FDCWD, dirB+"/"+n, unix.R_OK)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("%s: %v", n, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Opening a nonexistant file name should return ENOENT
|
// Opening a nonexistant file name should return ENOENT
|
||||||
// and not EBADMSG or EIO or anything else.
|
// and not EBADMSG or EIO or anything else.
|
||||||
func TestEnoent(t *testing.T) {
|
func TestEnoent(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue