e8d8ae54d3
Rename openBackingPath to openBackingDir and use OpenDirNofollow to be safe against symlink races. Note that openBackingDir is not used in several important code paths like Create(). But it is used in Unlink, and the performance impact in the RM benchmark to be acceptable: Before $ ./benchmark.bash Testing gocryptfs at /tmp/benchmark.bash.bYO: gocryptfs v1.6-12-g930c37e-dirty; go-fuse v20170619-49-gb11e293; 2018-09-08 go1.10.3 WRITE: 262144000 bytes (262 MB, 250 MiB) copied, 1.07979 s, 243 MB/s READ: 262144000 bytes (262 MB, 250 MiB) copied, 0.882413 s, 297 MB/s UNTAR: 16.703 MD5: 7.606 LS: 1.349 RM: 3.237 After $ ./benchmark.bash Testing gocryptfs at /tmp/benchmark.bash.jK3: gocryptfs v1.6-13-g84d6faf-dirty; go-fuse v20170619-49-gb11e293; 2018-09-08 go1.10.3 WRITE: 262144000 bytes (262 MB, 250 MiB) copied, 1.06261 s, 247 MB/s READ: 262144000 bytes (262 MB, 250 MiB) copied, 0.947228 s, 277 MB/s UNTAR: 17.197 MD5: 7.540 LS: 1.364 RM: 3.410
242 lines
6.3 KiB
Go
242 lines
6.3 KiB
Go
package reverse_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"runtime"
|
|
"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"
|
|
)
|
|
|
|
// TestLongnameStat checks that file names of all sizes (1 to 255) show up in
|
|
// the decrypted reverse view (dirC, mounted in TestMain).
|
|
func TestLongnameStat(t *testing.T) {
|
|
for i := 1; i <= 255; i++ {
|
|
name := string(bytes.Repeat([]byte("x"), i))
|
|
fd, err := os.Create(dirA + "/" + name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fd.Close()
|
|
path := dirC + "/" + name
|
|
if !test_helpers.VerifyExistence(path) {
|
|
t.Fatalf("failed to verify %q", path)
|
|
}
|
|
test_helpers.VerifySize(t, path, 0)
|
|
// A large number of longname files is a performance problem in
|
|
// reverse mode. Move the file out of the way once we are done with it
|
|
// to speed up the test (2 seconds -> 0.2 seconds).
|
|
// We do NOT unlink it because ext4 reuses inode numbers immediately,
|
|
// which will cause "Found linked inode, but Nlink == 1" warnings and
|
|
// file not found errors.
|
|
// TODO: This problem should be handled at the go-fuse level.
|
|
syscall.Rename(dirA+"/"+name, test_helpers.TmpDir+"/"+fmt.Sprintf("x%d", i))
|
|
}
|
|
}
|
|
|
|
func TestSymlinks(t *testing.T) {
|
|
target := "/"
|
|
os.Symlink(target, dirA+"/symlink")
|
|
cSymlink := dirC + "/symlink"
|
|
_, err := os.Lstat(cSymlink)
|
|
if err != nil {
|
|
t.Errorf("Lstat: %v", err)
|
|
}
|
|
_, err = os.Stat(cSymlink)
|
|
if err != nil {
|
|
t.Errorf("Stat: %v", err)
|
|
}
|
|
actualTarget, err := os.Readlink(cSymlink)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if target != actualTarget {
|
|
t.Errorf("wrong symlink target: want=%q have=%q", target, actualTarget)
|
|
}
|
|
}
|
|
|
|
// Symbolic link dentry sizes should be set to the length of the string
|
|
// that contains the target path.
|
|
func TestSymlinkDentrySize(t *testing.T) {
|
|
if plaintextnames {
|
|
t.Skip("this only tests encrypted names")
|
|
}
|
|
symlink := "a_symlink"
|
|
|
|
mnt, err := ioutil.TempDir(test_helpers.TmpDir, "reverse_mnt_")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sock := mnt + ".sock"
|
|
test_helpers.MountOrFatal(t, "ctlsock_reverse_test_fs", mnt, "-reverse", "-extpass", "echo test", "-ctlsock="+sock)
|
|
defer test_helpers.UnmountPanic(mnt)
|
|
|
|
req := ctlsock.RequestStruct{EncryptPath: symlink}
|
|
symlinkResponse := test_helpers.QueryCtlSock(t, sock, req)
|
|
if symlinkResponse.ErrNo != 0 {
|
|
t.Errorf("Encrypt: %q ErrNo=%d ErrText=%s", symlink, symlinkResponse.ErrNo, symlinkResponse.ErrText)
|
|
}
|
|
|
|
fi, err := os.Lstat(mnt + "/" + symlinkResponse.Result)
|
|
if err != nil {
|
|
t.Errorf("Lstat: %v", err)
|
|
}
|
|
|
|
target, err := os.Readlink(mnt + "/" + symlinkResponse.Result)
|
|
if err != nil {
|
|
t.Errorf("Readlink: %v", err)
|
|
}
|
|
|
|
if fi.Size() != int64(len(target)) {
|
|
t.Errorf("Lstat reports that symbolic link %q's dentry size is %d, but this does not "+
|
|
"match the length of the string returned by readlink, which is %d.",
|
|
symlink, fi.Size(), len(target))
|
|
}
|
|
}
|
|
|
|
// .gocryptfs.reverse.conf in the plaintext dir should be visible as
|
|
// gocryptfs.conf
|
|
func TestConfigMapping(t *testing.T) {
|
|
c := dirB + "/gocryptfs.conf"
|
|
if !test_helpers.VerifyExistence(c) {
|
|
t.Errorf("%s missing", c)
|
|
}
|
|
data, err := ioutil.ReadFile(c)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(data) == 0 {
|
|
t.Errorf("empty file")
|
|
}
|
|
}
|
|
|
|
// Check that the access() syscall works on virtual files
|
|
func TestAccessVirtual(t *testing.T) {
|
|
if plaintextnames {
|
|
t.Skip("test makes no sense for plaintextnames")
|
|
}
|
|
var R_OK uint32 = 4
|
|
var W_OK uint32 = 2
|
|
var X_OK uint32 = 1
|
|
fn := dirB + "/gocryptfs.diriv"
|
|
err := syscall.Access(fn, R_OK)
|
|
if err != nil {
|
|
t.Errorf("%q should be readable, but got error: %v", fn, err)
|
|
}
|
|
err = syscall.Access(fn, W_OK)
|
|
if err == nil {
|
|
t.Errorf("should NOT be writeable")
|
|
}
|
|
err = syscall.Access(fn, X_OK)
|
|
if err == nil {
|
|
t.Errorf("should NOT be executable")
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
defer f.Close()
|
|
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) {
|
|
fn := dirB + "/TestEnoent"
|
|
_, err := syscall.Open(fn, syscall.O_RDONLY, 0)
|
|
if err != syscall.ENOENT {
|
|
t.Errorf("want ENOENT, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// If the symlink target gets too long due to base64 encoding, we should
|
|
// return ENAMETOOLONG instead of having the kernel reject the data and
|
|
// returning an I/O error to the user.
|
|
// https://github.com/rfjakob/gocryptfs/issues/167
|
|
func TestTooLongSymlink(t *testing.T) {
|
|
l := 4000
|
|
if runtime.GOOS == "darwin" {
|
|
l = 1000 // max length is much lower on darwin
|
|
}
|
|
fn := dirA + "/TooLongSymlink"
|
|
target := string(bytes.Repeat([]byte("x"), l))
|
|
err := os.Symlink(target, fn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = os.Readlink(dirC + "/TooLongSymlink")
|
|
if err == nil {
|
|
return
|
|
}
|
|
err2 := err.(*os.PathError)
|
|
if err2.Err != syscall.ENAMETOOLONG {
|
|
t.Errorf("Expected %q error, got %q instead", syscall.ENAMETOOLONG,
|
|
err2.Err)
|
|
}
|
|
}
|
|
|
|
// Test that we can traverse a directory with 0100 permissions
|
|
// (execute but no read). This used to be a problem as OpenDirNofollow opened
|
|
// all directories in the path with O_RDONLY. Now it uses O_PATH, which only needs
|
|
// the executable bit.
|
|
func Test0100Dir(t *testing.T) {
|
|
// Note: t.Name() is not available before in Go 1.8
|
|
tName := "Test0100Dir"
|
|
dir := dirA + "/" + tName
|
|
err := os.Mkdir(dir, 0700)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
file := dir + "/hello"
|
|
err = ioutil.WriteFile(file, []byte("hello"), 0600)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = os.Chmod(dir, 0100)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
fileReverse := dirC + "/" + tName + "/hello"
|
|
fd, err := os.Open(fileReverse)
|
|
// Make sure the dir can be removed after the test is done
|
|
os.Chmod(dir, 0700)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fd.Close()
|
|
}
|