diff options
Diffstat (limited to 'tests')
-rwxr-xr-x | tests/issue893.sh | 27 | ||||
-rw-r--r-- | tests/plaintextnames/file_holes_test.go | 2 | ||||
-rw-r--r-- | tests/reverse/correctness_test.go | 97 | ||||
-rw-r--r-- | tests/root_test/issue893_test.go | 99 | ||||
-rw-r--r-- | tests/root_test/main_test.go | 20 | ||||
-rw-r--r-- | tests/root_test/root_test.go | 51 |
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) |