//+build linux // 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" "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat" "golang.org/x/sys/unix" "github.com/rfjakob/gocryptfs/v2/tests/test_helpers" ) func asUser(uid int, gid int, supplementaryGroups []int, f func() error) error { runtime.LockOSThread() defer runtime.UnlockOSThread() err := unix.Setgroups(supplementaryGroups) if err != nil { return err } defer func() { err = unix.Setgroups(nil) if err != nil { panic(err) } }() err = unix.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) if err != nil { return err } defer func() { err = unix.Setreuid(-1, 0) if err != nil { panic(err) } }() 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) { 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" err := os.Mkdir(dir1, 0770) if err != nil { t.Fatal(err) } err = os.Chown(dir1, 0, 1234) if err != nil { t.Fatal(err) } err = asUser(1235, 1235, []int{1234}, func() error { return os.Mkdir(dir1+"/dir2", 0700) }) if err != nil { t.Error(err) } err = asUser(1235, 1235, []int{1234}, func() error { f, err := os.Create(dir1 + "/file1") if err == nil { f.Close() } return err }) if err != nil { t.Error(err) } } func writeTillFull(t *testing.T, path string) (int, syscall.Errno) { runtime.LockOSThread() defer runtime.UnlockOSThread() fd, err := syscall.Open(path, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_TRUNC, 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 } } // TestDiskFull needs root permissions because it creates a loop disk 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 func() { const MNT_DETACH = 2 err := syscall.Unmount(ext4mnt, MNT_DETACH) if err != nil { t.Log(err) } }() // 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" 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() } } func TestAcl(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", "-acl", "-extpass=echo test") defer test_helpers.UnmountPanic(pDir) f1 := pDir + "/f1" if err := ioutil.WriteFile(f1, []byte("hello world\n"), 000); err != nil { t.Fatal(err) } openUser1234 := func(rwMode int) error { return asUser(1234, 1234, nil, func() error { fd, err := syscall.Open(f1, rwMode, 0) if err != nil { return err } defer syscall.Close(fd) buf := make([]byte, 100) if rwMode == syscall.O_RDONLY || rwMode == syscall.O_RDWR { _, err = syscall.Read(fd, buf) if err != nil { return err } } if rwMode == syscall.O_WRONLY || rwMode == syscall.O_RDWR { _, err = syscall.Write(fd, buf) if err != nil { return err } } return err }) } dumpAcl := func() { out, err := exec.Command("getfacl", f1).CombinedOutput() if err != nil { t.Fatal(err) } t.Log(string(out)) } if err := openUser1234(syscall.O_RDONLY); err == nil { t.Error("this should have failed") dumpAcl() } // Allow read out, err := exec.Command("setfacl", "-m", "u:1234:r", f1).CombinedOutput() if err != nil { t.Fatal(string(out)) } if err := openUser1234(syscall.O_RDONLY); err != nil { t.Errorf("O_RDONLY should have worked, but got error: %v", err) dumpAcl() } if err := openUser1234(syscall.O_WRONLY); err == nil { t.Error("O_WRONLY should have failed") dumpAcl() } // Allow write out, err = exec.Command("setfacl", "-m", "u:1234:w", f1).CombinedOutput() if err != nil { t.Fatal(string(out)) } if err := openUser1234(syscall.O_WRONLY); err != nil { t.Errorf("O_WRONLY should have worked, but got error: %v", err) dumpAcl() } if err := openUser1234(syscall.O_RDONLY); err == nil { t.Error("O_RDONLY should have failed") dumpAcl() } } // TestBtrfsQuirks needs root permissions because it creates a loop disk func TestBtrfsQuirks(t *testing.T) { if os.Getuid() != 0 { t.Skip("must run as root") } img := filepath.Join(test_helpers.TmpDir, t.Name()+".img") f, err := os.Create(img) if err != nil { t.Fatal(err) } defer f.Close() // minimum size for each btrfs device is 114294784 err = f.Truncate(200 * 1024 * 1024) if err != nil { t.Fatal(err) } // Format as Btrfs cmd := exec.Command("mkfs.btrfs", img) out, err := cmd.CombinedOutput() if err != nil { t.Logf("%q", cmd.Args) t.Log(string(out)) t.Fatal(err) } // Mount mnt := img + ".mnt" err = os.Mkdir(mnt, 0600) if err != nil { t.Fatal(err) } cmd = exec.Command("mount", img, mnt) out, err = cmd.CombinedOutput() if err != nil { t.Log(string(out)) t.Fatal(err) } defer syscall.Unlink(img) defer syscall.Unmount(mnt, 0) quirk := syscallcompat.DetectQuirks(mnt) if quirk != syscallcompat.QuirkBrokenFalloc { t.Errorf("wrong quirk: %v", quirk) } } func TestOverlay(t *testing.T) { if os.Getuid() != 0 { t.Skip("must run as root") } cDir := test_helpers.InitFS(t) if syscallcompat.DetectQuirks(cDir)|syscallcompat.QuirkNoUserXattr != 0 { t.Logf("No user xattrs! overlay mount will likely fail.") } os.Chmod(cDir, 0755) pDir := cDir + ".mnt" test_helpers.MountOrFatal(t, cDir, pDir, "-allow_other", "-extpass=echo test") defer test_helpers.UnmountPanic(pDir) for _, d := range []string{"lower", "upper", "work", "merged"} { err := os.Mkdir(pDir+"/"+d, 0700) if err != nil { t.Fatal(err) } } ovlMnt := pDir + "/merged" cmd := exec.Command("mount", "-t", "overlay", "overlay", "-o", "lowerdir="+pDir+"/lower,upperdir="+pDir+"/upper,workdir="+pDir+"/work", ovlMnt) out, err := cmd.CombinedOutput() if err != nil { t.Log(string(out)) t.Fatal(err) } defer syscall.Unmount(ovlMnt, 0) }