diff --git a/test.bash b/test.bash index 266a875..091f0ac 100755 --- a/test.bash +++ b/test.bash @@ -85,8 +85,7 @@ if grep -R "panic(" ./*.go internal ; then fi # All functions from the commit msg in https://go-review.googlesource.com/c/go/+/210639 -if grep -R -E 'syscall.(Setegid|Seteuid|Setgroups|Setgid|Setregid|Setreuid|Setresgid|Setresuid|Setuid)\(' \ - ./*.go internal ; then +if find . -type f -name \*.go -print0 | xargs -0 grep -E 'syscall.(Setegid|Seteuid|Setgroups|Setgid|Setregid|Setreuid|Setresgid|Setresuid|Setuid)\(' ; then echo "$MYNAME: You probably want to use unix.Setgroups and friends. See the comments in OpenatUser() for why." exit 1 fi diff --git a/tests/cli/cli_test.go b/tests/cli/cli_test.go index 23cea05..9d25062 100644 --- a/tests/cli/cli_test.go +++ b/tests/cli/cli_test.go @@ -9,6 +9,7 @@ import ( "os/exec" "strconv" "strings" + "sync" "syscall" "testing" "time" @@ -869,3 +870,35 @@ func TestSharedstorage(t *testing.T) { t.Fatal(st2.Size) } } + +// Test that the filesystem is immediately ready for Creat() after mount returns +func TestMountCreat(t *testing.T) { + const concurrency = 2 + const repeat = 2 + + dir := test_helpers.InitFS(t) + mnt := dir + ".mnt" + err := os.Mkdir(mnt, 0700) + if err != nil { + t.Fatal(err) + } + + for j := 0; j < repeat; j++ { + test_helpers.MountOrFatal(t, dir, mnt, "-extpass=echo test") + var wg sync.WaitGroup + wg.Add(concurrency) + for i := 0; i < concurrency; i++ { + go func(i int) { + path := fmt.Sprintf("%s/%d", mnt, i) + fd, err := syscall.Creat(path, 0600) + syscall.Close(fd) + if err != nil { + t.Errorf("Creat %q: %v", path, err) + } + wg.Done() + }(i) + } + wg.Wait() + test_helpers.UnmountPanic(mnt) + } +} diff --git a/tests/root_test/root_test.go b/tests/root_test/root_test.go index 26caafc..079c03b 100644 --- a/tests/root_test/root_test.go +++ b/tests/root_test/root_test.go @@ -1,11 +1,19 @@ +// Package root_test contains tests that need root +// permissions to run package root_test import ( + "io/ioutil" "os" + "os/exec" + "path/filepath" "runtime" + "sync" "syscall" "testing" + "golang.org/x/sys/unix" + "github.com/rfjakob/gocryptfs/tests/test_helpers" ) @@ -13,25 +21,57 @@ func asUser(uid int, gid int, supplementaryGroups []int, f func() error) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - err := syscall.Setgroups(supplementaryGroups) + err := unix.Setgroups(supplementaryGroups) if err != nil { return err } - defer syscall.Setgroups(nil) - - err = syscall.Setregid(-1, gid) + defer func() { + err = unix.Setgroups(nil) + if err != nil { + panic(err) + } + }() + err = unix.Setregid(-1, gid) if err != nil { return err } - defer syscall.Setregid(-1, 0) - - err = syscall.Setreuid(-1, uid) + defer func() { + err = unix.Setregid(-1, 0) + if err != nil { + panic(err) + } + }() + err = unix.Setreuid(-1, uid) if err != nil { return err } - defer syscall.Setreuid(-1, 0) + defer func() { + err = unix.Setreuid(-1, 0) + if err != nil { + panic(err) + } + }() - return f() + ret := f() + + // Also reset the saved user id (suid) and saved group id (sgid) to prevent + // bizarre failures in later tests. + // + // Yes, the kernel checks that *all of them* match: + // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/fuse/dir.c?h=v5.12-rc2#n1193 + // + // 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) + } + err = unix.Setresgid(0, 0, 0) + if err != nil { + panic(err) + } + + return ret } func TestSupplementaryGroups(t *testing.T) { @@ -73,3 +113,123 @@ func TestSupplementaryGroups(t *testing.T) { t.Error(err) } } + +func writeTillFull(t *testing.T, path string) (int, syscall.Errno) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + fd, err := syscall.Creat(path, 0600) + if err != nil { + return 0, err.(syscall.Errno) + } + defer syscall.Close(fd) + // Write in 100.000 byte-blocks, which is not aligend to the + // underlying block size + buf := make([]byte, 100000) + var sz int + for { + n, err := syscall.Write(fd, buf) + if err != nil { + return sz, err.(syscall.Errno) + } + sz += n + } + return sz, 0 +} + +func TestDiskFull(t *testing.T) { + if os.Getuid() != 0 { + t.Skip("must run as root") + } + + // Create 10 MB file full of zeros + ext4img := filepath.Join(test_helpers.TmpDir, t.Name()+".ext4") + f, err := os.Create(ext4img) + if err != nil { + t.Fatal(err) + } + defer f.Close() + err = f.Truncate(10 * 1024 * 1024) + if err != nil { + t.Fatal(err) + } + + // Format as ext4 + cmd := exec.Command("mkfs.ext4", ext4img) + out, err := cmd.CombinedOutput() + if err != nil { + t.Log(string(out)) + t.Fatal(err) + } + + // Mount ext4 + ext4mnt := ext4img + ".mnt" + err = os.Mkdir(ext4mnt, 0600) + if err != nil { + t.Fatal(err) + } + cmd = exec.Command("mount", ext4img, ext4mnt) + out, err = cmd.CombinedOutput() + if err != nil { + t.Log(string(out)) + t.Fatal(err) + } + defer syscall.Unlink(ext4img) + defer syscall.Unmount(ext4mnt, 0) + + // gocryptfs -init + cipherdir := ext4mnt + "/a" + if err = os.Mkdir(cipherdir, 0600); err != nil { + t.Fatal(err) + } + cmd = exec.Command(test_helpers.GocryptfsBinary, "-q", "-init", "-extpass", "echo test", "-scryptn=10", cipherdir) + out, err = cmd.CombinedOutput() + if err != nil { + t.Log(string(out)) + t.Fatal(err) + } + + // Mount gocryptfs + mnt := ext4mnt + "/b" + err = os.Mkdir(mnt, 0600) + if err != nil { + t.Fatal(err) + } + test_helpers.MountOrFatal(t, cipherdir, mnt, "-extpass", "echo test") + defer test_helpers.UnmountPanic(mnt) + + // Write till we get ENOSPC + var err1, err2 error + var sz1, sz2 int + var wg sync.WaitGroup + wg.Add(2) + go func() { + sz1, err1 = writeTillFull(t, mnt+"/foo1") + wg.Done() + }() + go func() { + sz2, err2 = writeTillFull(t, mnt+"/foo2") + wg.Done() + }() + wg.Wait() + if err1 != syscall.ENOSPC || err2 != syscall.ENOSPC { + t.Fatalf("err1=%v, err2=%v", err1, err2) + } + t.Logf("sz1=%d, sz2=%d", sz1, sz2) + + foo1, err := ioutil.ReadFile(mnt + "/foo1") + if err != nil { + t.Fatal(err) + } + if len(foo1) != sz1 { + t.Fail() + } + + foo2, err := ioutil.ReadFile(mnt + "/foo2") + if err != nil { + t.Fatal(err) + } + if len(foo2) != sz2 { + t.Fail() + } +}