2016-09-25 15:05:09 +02:00
|
|
|
package reverse_test
|
|
|
|
|
|
|
|
import (
|
2017-10-01 13:50:25 +02:00
|
|
|
"bytes"
|
2017-10-03 21:15:17 +02:00
|
|
|
"fmt"
|
2016-10-08 20:57:38 +02:00
|
|
|
"io/ioutil"
|
2016-09-25 15:05:09 +02:00
|
|
|
"os"
|
2017-02-16 21:20:29 +01:00
|
|
|
"syscall"
|
2016-09-25 15:05:09 +02:00
|
|
|
"testing"
|
2016-09-25 15:32:46 +02:00
|
|
|
|
2017-12-07 00:08:10 +01:00
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
|
2020-05-09 17:36:41 +02:00
|
|
|
"github.com/rfjakob/gocryptfs/ctlsock"
|
2019-01-20 13:10:59 +01:00
|
|
|
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
2016-09-25 15:32:46 +02:00
|
|
|
"github.com/rfjakob/gocryptfs/tests/test_helpers"
|
2016-09-25 15:05:09 +02:00
|
|
|
)
|
|
|
|
|
2017-10-01 13:50:25 +02:00
|
|
|
// TestLongnameStat checks that file names of all sizes (1 to 255) show up in
|
|
|
|
// the decrypted reverse view (dirC, mounted in TestMain).
|
2016-09-25 15:05:09 +02:00
|
|
|
func TestLongnameStat(t *testing.T) {
|
2017-10-01 13:50:25 +02:00
|
|
|
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) {
|
2017-10-03 21:15:17 +02:00
|
|
|
t.Fatalf("failed to verify %q", path)
|
2017-10-01 13:50:25 +02:00
|
|
|
}
|
|
|
|
test_helpers.VerifySize(t, path, 0)
|
|
|
|
// A large number of longname files is a performance problem in
|
2017-10-03 21:15:17 +02:00
|
|
|
// 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))
|
2016-09-25 15:05:09 +02:00
|
|
|
}
|
|
|
|
}
|
2016-09-25 18:01:24 +02:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2016-10-08 20:57:38 +02:00
|
|
|
|
2017-03-07 10:09:09 +01:00
|
|
|
// Symbolic link dentry sizes should be set to the length of the string
|
|
|
|
// that contains the target path.
|
|
|
|
func TestSymlinkDentrySize(t *testing.T) {
|
2017-03-07 20:53:58 +01:00
|
|
|
if plaintextnames {
|
|
|
|
t.Skip("this only tests encrypted names")
|
|
|
|
}
|
2017-03-07 10:09:09 +01:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-08 20:57:38 +02:00
|
|
|
// .gocryptfs.reverse.conf in the plaintext dir should be visible as
|
|
|
|
// gocryptfs.conf
|
|
|
|
func TestConfigMapping(t *testing.T) {
|
|
|
|
c := dirB + "/gocryptfs.conf"
|
2016-10-08 22:25:08 +02:00
|
|
|
if !test_helpers.VerifyExistence(c) {
|
|
|
|
t.Errorf("%s missing", c)
|
|
|
|
}
|
2016-10-08 20:57:38 +02:00
|
|
|
data, err := ioutil.ReadFile(c)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
|
|
t.Errorf("empty file")
|
|
|
|
}
|
|
|
|
}
|
2017-02-16 21:20:29 +01:00
|
|
|
|
|
|
|
// Check that the access() syscall works on virtual files
|
|
|
|
func TestAccessVirtual(t *testing.T) {
|
|
|
|
if plaintextnames {
|
2017-03-07 20:53:58 +01:00
|
|
|
t.Skip("test makes no sense for plaintextnames")
|
2017-02-16 21:20:29 +01:00
|
|
|
}
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
2017-07-27 20:31:22 +02:00
|
|
|
|
2017-12-07 00:08:10 +01:00
|
|
|
// 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)
|
|
|
|
}
|
2018-03-05 23:21:08 +01:00
|
|
|
defer f.Close()
|
2017-12-07 00:08:10 +01:00
|
|
|
names, err := f.Readdirnames(0)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
for _, n := range names {
|
|
|
|
// Check if file exists - this should never fail
|
2019-01-20 13:10:59 +01:00
|
|
|
err = syscallcompat.Faccessat(unix.AT_FDCWD, dirB+"/"+n, unix.F_OK)
|
2017-12-07 00:08:10 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("%s: %v", n, err)
|
|
|
|
}
|
|
|
|
// Check if file is readable
|
2019-01-20 13:10:59 +01:00
|
|
|
err = syscallcompat.Faccessat(unix.AT_FDCWD, dirB+"/"+n, unix.R_OK)
|
2017-12-07 00:08:10 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Logf("%s: %v", n, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-27 12:03:00 +01:00
|
|
|
// Opening a nonexistent file name should return ENOENT
|
2017-07-27 20:31:22 +02:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
2017-11-26 21:27:29 +01:00
|
|
|
|
|
|
|
// 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) {
|
2018-10-10 22:38:22 +02:00
|
|
|
var err error
|
|
|
|
var l int
|
2017-11-26 21:27:29 +01:00
|
|
|
fn := dirA + "/TooLongSymlink"
|
2018-10-10 22:38:22 +02:00
|
|
|
// Try 4000 first (works on ext4 and tmpfs), then retry with 1000 (XFS and
|
|
|
|
// Darwin have a limit of about 1024)
|
|
|
|
for _, l = range []int{4000, 1000} {
|
|
|
|
target := string(bytes.Repeat([]byte("x"), l))
|
|
|
|
err = os.Symlink(target, fn)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2017-11-26 21:27:29 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2020-05-03 13:23:00 +02:00
|
|
|
// save later tests the trouble of dealing with ENAMETOOLONG errors
|
|
|
|
defer func() {
|
|
|
|
os.Remove(fn)
|
|
|
|
// immediately create a new symlink so the inode number is not
|
|
|
|
// reused for something else
|
|
|
|
os.Symlink("/tmp", fn)
|
|
|
|
}()
|
2018-10-10 22:38:22 +02:00
|
|
|
t.Logf("Created symlink of length %d", l)
|
2017-11-26 21:27:29 +01:00
|
|
|
_, 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)
|
|
|
|
}
|
|
|
|
}
|
2018-09-08 18:06:33 +02:00
|
|
|
|
|
|
|
// Test that we can traverse a directory with 0100 permissions
|
|
|
|
// (execute but no read). This used to be a problem as OpenDirNofollow opened
|
fusefrontend: use OpenDirNofollow in openBackingDir
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
2018-09-08 19:27:33 +02:00
|
|
|
// all directories in the path with O_RDONLY. Now it uses O_PATH, which only needs
|
2018-09-08 18:06:33 +02:00
|
|
|
// the executable bit.
|
|
|
|
func Test0100Dir(t *testing.T) {
|
2020-05-09 15:42:57 +02:00
|
|
|
dir := dirA + "/" + t.Name()
|
2018-09-08 18:06:33 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-05-09 15:42:57 +02:00
|
|
|
fileReverse := dirC + "/" + t.Name() + "/hello"
|
2018-09-08 18:06:33 +02:00
|
|
|
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()
|
|
|
|
}
|