fusefronted: enable writing to write-only files

Due to RMW, we always need read permissions on the backing file. This is a
problem if the file permissions do not allow reading (i.e. 0200 permissions).
This patch works around that problem by chmod'ing the file, obtaining a fd,
and chmod'ing it back.

Test included.

Issue reported at: https://github.com/rfjakob/gocryptfs/issues/125
This commit is contained in:
Jakob Unterwurzacher 2017-07-11 23:19:58 +02:00
parent 849ec10081
commit 3062de6187
2 changed files with 89 additions and 4 deletions

View File

@ -34,6 +34,9 @@ type FS struct {
nameTransform *nametransform.NameTransform nameTransform *nametransform.NameTransform
// Content encryption helper // Content encryption helper
contentEnc *contentenc.ContentEnc contentEnc *contentenc.ContentEnc
// This lock is used by openWriteOnlyFile() to block concurrent opens while
// it relaxes the permissions on a file.
openWriteOnlyLock sync.RWMutex
} }
var _ pathfs.FileSystem = &FS{} // Verify that interface is implemented. var _ pathfs.FileSystem = &FS{} // Verify that interface is implemented.
@ -102,6 +105,10 @@ func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile n
if fs.isFiltered(path) { if fs.isFiltered(path) {
return nil, fuse.EPERM return nil, fuse.EPERM
} }
// Taking this lock makes sure we don't race openWriteOnlyFile()
fs.openWriteOnlyLock.RLock()
defer fs.openWriteOnlyLock.RUnlock()
newFlags := fs.mangleOpenFlags(flags) newFlags := fs.mangleOpenFlags(flags)
cPath, err := fs.getBackingPath(path) cPath, err := fs.getBackingPath(path)
if err != nil { if err != nil {
@ -109,20 +116,68 @@ func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile n
return nil, fuse.ToStatus(err) return nil, fuse.ToStatus(err)
} }
tlog.Debug.Printf("Open: %s", cPath) tlog.Debug.Printf("Open: %s", cPath)
f, err := os.OpenFile(cPath, newFlags, 0666) f, err := os.OpenFile(cPath, newFlags, 0)
if err != nil { if err != nil {
err2 := err.(*os.PathError) sysErr := err.(*os.PathError).Err
if err2.Err == syscall.EMFILE { if sysErr == syscall.EMFILE {
var lim syscall.Rlimit var lim syscall.Rlimit
syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim) 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", cPath, lim.Cur)
} }
if sysErr == syscall.EACCES && (int(flags)&os.O_WRONLY > 0) {
return fs.openWriteOnlyFile(cPath, newFlags)
}
return nil, fuse.ToStatus(err) return nil, fuse.ToStatus(err)
} }
return NewFile(f, fs) return NewFile(f, fs)
} }
// Due to RMW, we always need read permissions on the backing file. This is a
// 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)
if err != nil {
return nil, fuse.ToStatus(err)
}
defer woFd.Close()
fi, err := woFd.Stat()
if err != nil {
return nil, fuse.ToStatus(err)
}
perms := fi.Mode().Perm()
// Verify that we don't have read permissions
if perms&0400 != 0 {
tlog.Warn.Printf("openWriteOnlyFile: unexpected permissions %#o, returning EPERM", perms)
return nil, fuse.ToStatus(syscall.EPERM)
}
// Upgrade the lock to block other Open()s and downgrade again on return
fs.openWriteOnlyLock.RUnlock()
fs.openWriteOnlyLock.Lock()
defer func() {
fs.openWriteOnlyLock.Unlock()
fs.openWriteOnlyLock.RLock()
}()
// Relax permissions and revert on return
err = woFd.Chmod(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)
if err2 != nil {
tlog.Warn.Printf("openWriteOnlyFile: reverting permissions failed: %v", err2)
}
}()
rwFd, err := os.OpenFile(cPath, newFlags, 0)
if err != nil {
return nil, fuse.ToStatus(err)
}
return NewFile(rwFd, fs)
}
// Create implements pathfs.Filesystem. // Create implements pathfs.Filesystem.
func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) { func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) {
if fs.isFiltered(path) { if fs.isFiltered(path) {

View File

@ -4,6 +4,7 @@ package defaults
import ( import (
"bytes" "bytes"
"io" "io"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
@ -161,3 +162,32 @@ func TestXfs124(t *testing.T) {
wg.Wait() wg.Wait()
} }
func TestWrite0200File(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestWrite0200File"
err := ioutil.WriteFile(fn, nil, 0200)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
fd, err := os.OpenFile(fn, os.O_WRONLY, 0)
if err != nil {
t.Fatal(err)
}
fi, err := fd.Stat()
if err != nil {
t.Fatal(err)
}
perms := fi.Mode().Perm()
if perms != 0200 {
t.Fatal("wrong initial permissions")
}
defer fd.Close()
_, err = fd.Write(make([]byte, 10))
if err != nil {
t.Fatal(err)
}
perms = fi.Mode().Perm()
if perms != 0200 {
t.Fatal("wrong restored permissions")
}
}