aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rwxr-xr-xtests/issue893.sh27
-rw-r--r--tests/plaintextnames/file_holes_test.go2
-rw-r--r--tests/reverse/correctness_test.go97
-rw-r--r--tests/root_test/issue893_test.go99
-rw-r--r--tests/root_test/main_test.go20
-rw-r--r--tests/root_test/root_test.go51
6 files changed, 246 insertions, 50 deletions
diff --git a/tests/issue893.sh b/tests/issue893.sh
new file mode 100755
index 0000000..a1e7cdb
--- /dev/null
+++ b/tests/issue893.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# Reproducer for https://github.com/rfjakob/gocryptfs/issues/893 .
+# Run this script as non-root against a root-mounted gocryptfs -allow_other.
+
+set -eu
+
+mountpoint $1
+cd $1
+
+work() {
+ for i in $(seq 100) ; do
+ D=mtest.$BASHPID.$i/foo/bar/baz
+ mkdir -p $D
+ touch $D/foo $D/bar
+ echo AAAAAAAAAAAAAAAAAAAAA > $D/foo
+ rm $D/foo
+ mkdir $D/baz
+ done
+}
+
+rm -Rf mtest.*
+echo .
+
+work &
+work &
+
+wait
diff --git a/tests/plaintextnames/file_holes_test.go b/tests/plaintextnames/file_holes_test.go
index a17597a..ea47113 100644
--- a/tests/plaintextnames/file_holes_test.go
+++ b/tests/plaintextnames/file_holes_test.go
@@ -129,6 +129,8 @@ func doTestFileHoleCopy(t *testing.T, name string, writeOffsets []int64) {
// The test runs with -plaintextnames because that makes it easier to manipulate
// cipherdir directly.
func TestFileHoleCopy(t *testing.T) {
+ t.Skip("TODO: find out why this fails on recent kernels")
+
// | hole | x | hole | x | hole |
// truncate -s 50000 foo && dd if=/dev/zero of=foo bs=1 seek=10000 count=1 conv=notrunc && dd if=/dev/zero of=foo bs=1 seek=30000 count=1 conv=notrunc
name := "c0"
diff --git a/tests/reverse/correctness_test.go b/tests/reverse/correctness_test.go
index b335456..e4684df 100644
--- a/tests/reverse/correctness_test.go
+++ b/tests/reverse/correctness_test.go
@@ -295,6 +295,23 @@ func TestSeekData(t *testing.T) {
f.Close()
}
+// newWorkdir creates a new empty dir in dirA and returns the full path to it along
+// with the corresponding encrypted path in dirB
+func newWorkdir(t *testing.T) (workdirA, workdirB string) {
+ workdirA = dirA + "/" + t.Name()
+ if err := os.Mkdir(workdirA, 0700); err != nil {
+ t.Fatal(err)
+ }
+ // Find workdir in dirB (=encrypted view)
+ var st syscall.Stat_t
+ if err := syscall.Stat(workdirA, &st); err != nil {
+ t.Fatal(err)
+ }
+ workdirB = dirB + "/" + findIno(dirB, st.Ino)
+ t.Logf("newWorkdir: workdirA=%q workdirB=%q", workdirA, workdirB)
+ return
+}
+
// gocryptfs.longname.*.name of hardlinked files should not appear hardlinked (as the
// contents are different).
//
@@ -308,28 +325,22 @@ func TestHardlinkedLongname(t *testing.T) {
t.Skip()
}
- workdir := dirA + "/" + t.Name()
- if err := os.Mkdir(workdir, 0700); err != nil {
- t.Fatal(err)
- }
- long1 := workdir + "/" + strings.Repeat("x", 200)
+ workdirA, workdirB := newWorkdir(t)
+
+ long1 := workdirA + "/" + strings.Repeat("x", 200)
if err := ioutil.WriteFile(long1, []byte("hello"), 0600); err != nil {
t.Fatal(err)
}
- long2 := workdir + "/" + strings.Repeat("y", 220)
- if err := syscall.Link(long1, long2); err != nil {
+ var long1_stat syscall.Stat_t
+ if err := syscall.Stat(long1, &long1_stat); err != nil {
t.Fatal(err)
}
-
- // Find workdir in encrypted view
- var st syscall.Stat_t
- if err := syscall.Stat(workdir, &st); err != nil {
+ long2 := workdirA + "/" + strings.Repeat("y", 220)
+ if err := syscall.Link(long1, long2); err != nil {
t.Fatal(err)
}
- cWorkdir := dirB + "/" + findIno(dirB, st.Ino)
- t.Logf("workdir=%q cWorkdir=%q", workdir, cWorkdir)
- matches, err := filepath.Glob(cWorkdir + "/gocryptfs.longname.*.name")
+ matches, err := filepath.Glob(workdirB + "/gocryptfs.longname.*.name")
if err != nil {
t.Fatal(err)
}
@@ -352,3 +363,61 @@ func TestHardlinkedLongname(t *testing.T) {
t.Errorf("Files %q have the same inode number - that's wrong!", matches)
}
}
+
+// With inode number reuse and hard links, we could have returned
+// wrong data for gocryptfs.diriv and gocryptfs.xyz.longname files, respectively
+// (https://github.com/rfjakob/gocryptfs/issues/802).
+//
+// Now that this is fixed, ensure that rsync and similar tools pick up the new
+// correct files by advancing mtime and ctime by 10 seconds, which should be more
+// than any filesytems' timestamp granularity (FAT32 has 2 seconds).
+func TestMtimePlus10(t *testing.T) {
+ if plaintextnames {
+ t.Skip("plaintextnames mode does not have virtual files")
+ }
+
+ workdirA, workdirB := newWorkdir(t)
+
+ long := workdirA + "/" + strings.Repeat("x", 200)
+ if err := os.WriteFile(long, nil, 0600); err != nil {
+ t.Fatal(err)
+ }
+ long_stat, err := os.Stat(long)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ workdirA_stat, err := os.Stat(workdirA)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Find and check gocryptfs.longname.*.name
+ matches, err := filepath.Glob(workdirB + "/gocryptfs.longname.*.name")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(matches) != 1 {
+ t.Fatal(matches)
+ }
+ name_stat, err := os.Stat(matches[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ if name_stat.ModTime().Unix() != long_stat.ModTime().Unix()+10 {
+ t.Errorf(".name file should show mtime+10")
+ }
+
+ // Check gocryptfs.diriv
+ if deterministic_names {
+ // No gocryptfs.diriv
+ return
+ }
+ diriv_stat, err := os.Stat(workdirB + "/gocryptfs.diriv")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if diriv_stat.ModTime().Unix() != workdirA_stat.ModTime().Unix()+10 {
+ t.Errorf("diriv file should show mtime+10")
+ }
+}
diff --git a/tests/root_test/issue893_test.go b/tests/root_test/issue893_test.go
new file mode 100644
index 0000000..6ad8e6d
--- /dev/null
+++ b/tests/root_test/issue893_test.go
@@ -0,0 +1,99 @@
+//go:build linux
+
+package root_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
+)
+
+// gocryptfs v2.5.0 upgraded x/sys/unix and we found out that, since
+// https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51 ,
+// unix.Setreuid() and friends now affect the whole process instead of only the
+// current thread, breaking allow_other: https://github.com/rfjakob/gocryptfs/issues/893
+//
+// Let's not have this happen again by testing it here.
+func TestConcurrentUserOps(t *testing.T) {
+ if os.Getuid() != 0 {
+ t.Skip("must run as root")
+ }
+
+ var wg sync.WaitGroup
+
+ oneStressor := func(tid int) {
+ defer wg.Done()
+ err := asUser(10000+tid, 20000+tid, nil, func() (err error) {
+ for i := 0; i < 100; i++ {
+ d := fmt.Sprintf("%s/tid%d.i%d/foo/bar/baz", test_helpers.DefaultPlainDir, tid, i)
+ if err = os.MkdirAll(d, 0700); err != nil {
+ return
+ }
+ if err = ioutil.WriteFile(d+"/foo", nil, 0400); err != nil {
+ return
+ }
+ if err = ioutil.WriteFile(d+"/bar", []byte("aaaaaaaaaaaaaaaaaaaaa"), 0400); err != nil {
+ return
+ }
+ if err = syscall.Unlink(d + "/foo"); err != nil {
+ return
+ }
+ if err = os.Mkdir(d+"/foo", 0700); err != nil {
+ return
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ t.Error(err)
+ }
+ }
+
+ threads := 4
+ wg.Add(threads)
+ for tid := 0; tid < threads; tid++ {
+ go oneStressor(tid)
+ }
+ wg.Wait()
+}
+
+// Test that our root_test.asUser function works as expected under concurrency by
+// similating a long-runnig operation with sleep(10ms).
+// https://github.com/rfjakob/gocryptfs/issues/893
+func TestAsUserSleep(t *testing.T) {
+ if os.Getuid() != 0 {
+ t.Skip("must run as root")
+ }
+
+ var wg sync.WaitGroup
+ f := func(euid_want int) error {
+ euid_have := syscall.Geteuid()
+ if euid_want != euid_have {
+ return fmt.Errorf("wrong euid: want=%d have=%d", euid_want, euid_have)
+ }
+ time.Sleep(10 * time.Millisecond)
+ euid_have2 := syscall.Geteuid()
+ if euid_want != euid_have2 {
+ return fmt.Errorf("wrong euid: want=%d have2=%d", euid_want, euid_have2)
+ }
+ return nil
+ }
+ threads := 100
+ wg.Add(threads)
+ for i := 0; i < threads; i++ {
+ go func(i int) {
+ defer wg.Done()
+ err := asUser(10000+i, 20000+i, nil, func() error { return f(10000 + i) })
+ if err != nil {
+ t.Error(err)
+ }
+ }(i)
+ }
+ wg.Wait()
+}
diff --git a/tests/root_test/main_test.go b/tests/root_test/main_test.go
new file mode 100644
index 0000000..d6d5cc4
--- /dev/null
+++ b/tests/root_test/main_test.go
@@ -0,0 +1,20 @@
+//go:build linux
+
+package root_test
+
+import (
+ "os"
+ "testing"
+
+ "github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
+)
+
+func TestMain(m *testing.M) {
+ test_helpers.ResetTmpDir(true)
+ os.Chmod(test_helpers.DefaultCipherDir, 0777)
+ test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, "-zerokey", "-allow_other")
+ r := m.Run()
+ test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
+ os.RemoveAll(test_helpers.TmpDir)
+ os.Exit(r)
+}
diff --git a/tests/root_test/root_test.go b/tests/root_test/root_test.go
index 23b44d0..e432ce0 100644
--- a/tests/root_test/root_test.go
+++ b/tests/root_test/root_test.go
@@ -1,5 +1,4 @@
//go:build linux
-// +build linux
// Package root_test contains tests that need root
// permissions to run
@@ -17,8 +16,6 @@ import (
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
- "golang.org/x/sys/unix"
-
"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
)
@@ -26,36 +23,23 @@ func asUser(uid int, gid int, supplementaryGroups []int, f func() error) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
- err := unix.Setgroups(supplementaryGroups)
+ err := syscallcompat.Setgroups(supplementaryGroups)
if err != nil {
return err
}
- defer func() {
- err = unix.Setgroups(nil)
- if err != nil {
- panic(err)
- }
- }()
- err = unix.Setregid(-1, gid)
+ defer syscallcompat.SetgroupsPanic(nil)
+
+ err = syscallcompat.Setregid(-1, gid)
if err != nil {
return err
}
- defer func() {
- err = unix.Setregid(-1, 0)
- if err != nil {
- panic(err)
- }
- }()
- err = unix.Setreuid(-1, uid)
+ defer syscallcompat.SetregidPanic(-1, 0)
+
+ err = syscallcompat.Setreuid(-1, uid)
if err != nil {
return err
}
- defer func() {
- err = unix.Setreuid(-1, 0)
- if err != nil {
- panic(err)
- }
- }()
+ defer syscallcompat.SetreuidPanic(-1, 0)
ret := f()
@@ -67,13 +51,13 @@ func asUser(uid int, gid int, supplementaryGroups []int, f func() error) error {
//
// How to check:
// ps -o tid,pid,euid,ruid,suid,egid,rgid,sgid,cmd -eL
- err = unix.Setresuid(0, 0, 0)
- if err != nil {
- panic(err)
+ _, _, errno := syscall.RawSyscall(syscall.SYS_SETRESUID, uintptr(0), uintptr(0), uintptr(0))
+ if errno != 0 {
+ panic(errno)
}
- err = unix.Setresgid(0, 0, 0)
- if err != nil {
- panic(err)
+ _, _, errno = syscall.RawSyscall(syscall.SYS_SETRESGID, uintptr(0), uintptr(0), uintptr(0))
+ if errno != 0 {
+ panic(errno)
}
return ret
@@ -83,16 +67,11 @@ func TestSupplementaryGroups(t *testing.T) {
if os.Getuid() != 0 {
t.Skip("must run as root")
}
- cDir := test_helpers.InitFS(t)
- os.Chmod(cDir, 0755)
- pDir := cDir + ".mnt"
- test_helpers.MountOrFatal(t, cDir, pDir, "-allow_other", "-extpass=echo test")
- defer test_helpers.UnmountPanic(pDir)
// We need an unrestricted umask
syscall.Umask(0000)
- dir1 := pDir + "/dir1"
+ dir1 := test_helpers.DefaultPlainDir + "/dir1"
err := os.Mkdir(dir1, 0770)
if err != nil {
t.Fatal(err)