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
|
||||
}
|
||||
absPath, err := rfs.abs(rfs.decryptPath(relPath))
|
||||
dirfd, name, err := rfs.openBackingDir(relPath)
|
||||
if err != nil {
|
||||
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
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
||||
"github.com/rfjakob/gocryptfs/internal/pathiv"
|
||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||
"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))
|
||||
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"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/ctlsock"
|
||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||
"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
|
||||
// and not EBADMSG or EIO or anything else.
|
||||
func TestEnoent(t *testing.T) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user