aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2018-09-22 19:40:48 +0200
committerJakob Unterwurzacher2018-09-23 12:17:26 +0200
commit22fba4ac3ed09ba07d1defb60436fb7a17095d09 (patch)
tree3c2748f70910b3315756bf8e3d43588c70889d40
parent05c8d4a1c459dc4c73918af898119b1e17b1c0ce (diff)
fusefrontent: make Open() symlink-safe
-rw-r--r--internal/fusefrontend/fs.go45
1 files changed, 24 insertions, 21 deletions
diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go
index d68939b..0e198d6 100644
--- a/internal/fusefrontend/fs.go
+++ b/internal/fusefrontend/fs.go
@@ -96,6 +96,7 @@ func (fs *FS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Stat
// mangleOpenFlags is used by Create() and Open() to convert the open flags the user
// wants to the flags we internally use to open the backing file.
+// The returned flags always contain O_NOFOLLOW.
func (fs *FS) mangleOpenFlags(flags uint32) (newFlags int) {
newFlags = int(flags)
// Convert WRONLY to RDWR. We always need read access to do read-modify-write cycles.
@@ -109,7 +110,8 @@ func (fs *FS) mangleOpenFlags(flags uint32) (newFlags int) {
// accesses. Running xfstests generic/013 on ext4 used to trigger lots of
// EINVAL errors due to missing alignment. Just fall back to buffered IO.
newFlags = newFlags &^ syscallcompat.O_DIRECT
-
+ // We always want O_NOFOLLOW to be safe against symlink races
+ newFlags |= syscall.O_NOFOLLOW
return newFlags
}
@@ -118,30 +120,29 @@ func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile n
if fs.isFiltered(path) {
return nil, fuse.EPERM
}
+ newFlags := fs.mangleOpenFlags(flags)
// Taking this lock makes sure we don't race openWriteOnlyFile()
fs.openWriteOnlyLock.RLock()
defer fs.openWriteOnlyLock.RUnlock()
-
- newFlags := fs.mangleOpenFlags(flags)
- cPath, err := fs.getBackingPath(path)
+ // Symlink-safe open
+ dirfd, cName, err := fs.openBackingDir(path)
if err != nil {
- tlog.Debug.Printf("Open: getBackingPath: %v", err)
return nil, fuse.ToStatus(err)
}
- tlog.Debug.Printf("Open: %s", cPath)
- f, err := os.OpenFile(cPath, newFlags, 0)
+ fd, err := syscallcompat.Openat(int(dirfd.Fd()), cName, newFlags, 0)
+ // Handle a few specific errors
if err != nil {
- sysErr := err.(*os.PathError).Err
- if sysErr == syscall.EMFILE {
+ if err == syscall.EMFILE {
var lim syscall.Rlimit
syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim)
- tlog.Warn.Printf("Open %q: too many open files. Current \"ulimit -n\": %d", cPath, lim.Cur)
+ tlog.Warn.Printf("Open %q: too many open files. Current \"ulimit -n\": %d", cName, lim.Cur)
}
- if sysErr == syscall.EACCES && (int(flags)&os.O_WRONLY > 0) {
- return fs.openWriteOnlyFile(cPath, newFlags)
+ if err == syscall.EACCES && (int(flags)&os.O_WRONLY > 0) {
+ return fs.openWriteOnlyFile(dirfd, cName, newFlags)
}
return nil, fuse.ToStatus(err)
}
+ f := os.NewFile(uintptr(fd), cName)
return NewFile(f, fs)
}
@@ -149,17 +150,18 @@ func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile n
// problem if the file permissions do not allow reading (i.e. 0200 permissions).
// This function works around that problem by chmod'ing the file, obtaining a fd,
// and chmod'ing it back.
-func (fs *FS) openWriteOnlyFile(cPath string, newFlags int) (fuseFile nodefs.File, status fuse.Status) {
- woFd, err := os.OpenFile(cPath, os.O_WRONLY, 0)
+func (fs *FS) openWriteOnlyFile(dirfd *os.File, cName string, newFlags int) (fuseFile nodefs.File, status fuse.Status) {
+ woFd, err := syscallcompat.Openat(int(dirfd.Fd()), cName, syscall.O_WRONLY|syscall.O_NOFOLLOW, 0)
if err != nil {
return nil, fuse.ToStatus(err)
}
- defer woFd.Close()
- fi, err := woFd.Stat()
+ defer syscall.Close(woFd)
+ var st syscall.Stat_t
+ err = syscall.Fstat(woFd, &st)
if err != nil {
return nil, fuse.ToStatus(err)
}
- perms := fi.Mode().Perm()
+ perms := st.Mode & 0777
// Verify that we don't have read permissions
if perms&0400 != 0 {
tlog.Warn.Printf("openWriteOnlyFile: unexpected permissions %#o, returning EPERM", perms)
@@ -173,22 +175,23 @@ func (fs *FS) openWriteOnlyFile(cPath string, newFlags int) (fuseFile nodefs.Fil
fs.openWriteOnlyLock.RLock()
}()
// Relax permissions and revert on return
- err = woFd.Chmod(perms | 0400)
+ syscall.Fchmod(woFd, perms|0400)
if err != nil {
tlog.Warn.Printf("openWriteOnlyFile: changing permissions failed: %v", err)
return nil, fuse.ToStatus(err)
}
defer func() {
- err2 := woFd.Chmod(perms)
+ err2 := syscall.Fchmod(woFd, perms)
if err2 != nil {
tlog.Warn.Printf("openWriteOnlyFile: reverting permissions failed: %v", err2)
}
}()
- rwFd, err := os.OpenFile(cPath, newFlags, 0)
+ rwFd, err := syscallcompat.Openat(int(dirfd.Fd()), cName, newFlags, 0)
if err != nil {
return nil, fuse.ToStatus(err)
}
- return NewFile(rwFd, fs)
+ f := os.NewFile(uintptr(rwFd), cName)
+ return NewFile(f, fs)
}
// Create implements pathfs.Filesystem.