libgocryptfs/tests/defaults/main_test.go

494 lines
13 KiB
Go
Raw Normal View History

// Tests and benchmarks performed with default settings only.
package defaults
import (
"bytes"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"testing"
"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
)
func TestMain(m *testing.M) {
test_helpers.ResetTmpDir(true)
// TestZerokey() in tests/cli verifies that mounting with `-zerokey` is equivalent
// to mounting with a config file with all-default options (just the masterkey
// set to all-zero).
test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, "-zerokey")
r := m.Run()
test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
os.Exit(r)
}
// Test that we get the right timestamp when extracting a tarball.
func Test1980Tar(t *testing.T) {
c := exec.Command("tar", "xzf", "1980.tar.gz", "-C", test_helpers.DefaultPlainDir)
c.Stderr = os.Stderr
c.Stdout = os.Stdout
err := c.Run()
if err != nil {
t.Fatal(err)
}
fi, err := os.Stat(test_helpers.DefaultPlainDir + "/1980.txt")
if err != nil {
t.Fatal(err)
}
m := fi.ModTime().Unix()
if m != 315619323 {
t.Errorf("Wrong mtime: %d", m)
}
}
// In gocryptfs before v1.2, the file header was only read once for each
// open. But truncating a file to zero will generate a new random file ID.
// The sequence below caused an I/O error to be returned.
func TestOpenTruncateRead(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestTruncateWrite"
// First FD is used for write and truncate.
writeFd, err := os.Create(fn)
if err != nil {
t.Fatal(err)
}
defer writeFd.Close()
abc := []byte("abc")
_, err = writeFd.WriteAt(abc, 0)
if err != nil {
t.Fatal(err)
}
// Second FD is just for reading.
readFd, err := os.Open(fn)
if err != nil {
t.Fatal(err)
}
defer readFd.Close()
content := make([]byte, 3)
_, err = readFd.ReadAt(content, 0)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(content, abc) {
t.Fatalf("wrong content: %s", string(content))
}
// Truncate to zero to generate a new file ID and write new content.
err = writeFd.Truncate(0)
if err != nil {
t.Fatal(err)
}
xyz := []byte("xyz")
_, err = writeFd.WriteAt(xyz, 0)
if err != nil {
t.Fatal(err)
}
// Try to read from the other FD.
_, err = readFd.ReadAt(content, 0)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(content, xyz) {
t.Fatalf("wrong content: %s", string(content))
}
}
// TestWORead tries to read from a write-only FD.
func TestWORead(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestWORead"
fd, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
t.Fatal(err)
}
defer fd.Close()
buf := make([]byte, 10)
_, err = fd.Read(buf)
if err == nil {
t.Error("Reading from write-only file should fail, but did not")
}
}
// xfstests generic/124 triggers this warning:
// cipherSize 18 == header size: interrupted write?
// This test reproduces the problem.
func TestXfs124(t *testing.T) {
// GOMAXPROCS=8 and N=5000 seem to reliably trigger the problem. With N=1000,
// the test passes sometimes.
runtime.GOMAXPROCS(8)
N := 5000
fn := test_helpers.DefaultPlainDir + "/TestXfs124"
fd, err := os.Create(fn)
if err != nil {
t.Fatal(err)
}
defer fd.Close()
var wg sync.WaitGroup
wg.Add(2)
go func() {
buf := make([]byte, 10)
var err2 error
for i := 0; i < N; i++ {
err2 = fd.Truncate(0)
if err2 != nil {
panic(err2)
}
_, err2 = fd.WriteAt(buf, 0)
if err2 != nil {
panic(err2)
}
}
wg.Done()
}()
fd2, err := os.Open(fn)
if err != nil {
t.Fatal(err)
}
defer fd2.Close()
go func() {
buf := make([]byte, 10)
var err3 error
for i := 0; i < N; i++ {
_, err3 = fd2.ReadAt(buf, 0)
if err3 == io.EOF {
continue
}
if err3 != nil {
panic(err3)
}
}
wg.Done()
}()
wg.Wait()
}
func TestWrite0200File(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestWrite0200File"
err := ioutil.WriteFile(fn, nil, 0200)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
fd, err := os.OpenFile(fn, os.O_WRONLY, 0)
if err != nil {
t.Fatal(err)
}
fi, err := fd.Stat()
if err != nil {
t.Fatal(err)
}
perms := fi.Mode().Perm()
if perms != 0200 {
t.Fatal("wrong initial permissions")
}
defer fd.Close()
_, err = fd.Write(make([]byte, 10))
if err != nil {
t.Fatal(err)
}
perms = fi.Mode().Perm()
if perms != 0200 {
t.Fatal("wrong restored permissions")
}
}
2020-07-12 13:43:34 +02:00
// TestMvWarnings:
// When xattr support was introduced, mv threw warnings like these:
// mv: preserving permissions for b/x: Operation not permitted
// because we returned EPERM when it tried to set system.posix_acl_access.
// Now we return EOPNOTSUPP and mv is happy.
func TestMvWarnings(t *testing.T) {
fn := test_helpers.TmpDir + "/TestMvWarnings"
err := ioutil.WriteFile(fn, nil, 0600)
if err != nil {
t.Fatalf("creating file failed: %v", err)
}
cmd := exec.Command("mv", fn, test_helpers.DefaultPlainDir)
out, err := cmd.CombinedOutput()
if err != nil {
t.Log(string(out))
t.Fatal(err)
}
if len(out) != 0 {
t.Fatalf("Got warnings from mv:\n%s", string(out))
}
}
// Check for this bug in symlink handling:
// $ ln -s /asd/asdasd/asdasd b/foo
// $ mv b/foo .
// mv: listing attributes of 'b/foo': No such file or directory
// strace shows:
// llistxattr("b/foo", NULL, 0) = -1 ENOENT (No such file or directory)
func TestMvWarningSymlink(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestMvWarningSymlink"
err := os.Symlink("/foo/bar/baz", fn)
if err != nil {
t.Fatal(err)
}
cmd := exec.Command("mv", fn, test_helpers.TmpDir)
out, err := cmd.CombinedOutput()
if err != nil {
t.Log(string(out))
if runtime.GOOS == "darwin" {
t.Skip("mv on darwin chokes on broken symlinks, see https://github.com/rfjakob/gocryptfs/issues/349")
}
t.Fatal(err)
}
if len(out) != 0 {
t.Log(strings.TrimSpace(string(out)))
t.Fatal("Got warnings")
}
}
2020-07-12 13:43:34 +02:00
// See TestMvWarnings.
func TestCpWarnings(t *testing.T) {
fn := test_helpers.TmpDir + "/TestCpWarnings"
err := ioutil.WriteFile(fn, []byte("foo"), 0600)
if err != nil {
t.Fatalf("creating file failed: %v", err)
}
cmd := exec.Command("cp", "-a", fn, test_helpers.DefaultPlainDir)
out, err := cmd.CombinedOutput()
if err != nil {
t.Log(string(out))
t.Fatal(err)
}
if len(out) != 0 {
t.Fatalf("Got warnings from cp -a:\n%s", string(out))
}
}
2020-08-16 19:58:47 +02:00
// TestSeekData tests that fs.FileLseeker is implemented
func TestSeekData(t *testing.T) {
fn := filepath.Join(test_helpers.DefaultPlainDir, t.Name())
f, err := os.Create(fn)
if err != nil {
t.Fatal(err)
}
var oneTiB int64 = 1024 * 1024 * 1024 * 1024
if _, err = f.Seek(oneTiB, 0); err != nil {
t.Fatal(err)
}
if _, err = f.Write([]byte("foo")); err != nil {
t.Fatal(err)
}
f.Close()
const SEEK_DATA = 3
f, err = os.Open(fn)
if err != nil {
t.Fatal(err)
}
off, err := f.Seek(1024*1024, SEEK_DATA)
if err != nil {
t.Fatal(err)
}
if off < oneTiB-1024*1024 {
t.Errorf("off=%d, expected=%d\n", off, oneTiB)
}
f.Close()
}
/*
TestMd5sumMaintainers tries to repro this interesting
bug that was seen during gocryptfs v2.0 development:
$ md5sum linux-3.0/MAINTAINERS linux-3.0/MAINTAINERS linux-3.0/MAINTAINERS linux-3.0/MAINTAINERS
279b6ab0491e7532132e8f32afe6c04d linux-3.0/MAINTAINERS <-- WRONG!!!!
99cc9f0dfd86e63231b94edd43a43e02 linux-3.0/MAINTAINERS <-- correct
99cc9f0dfd86e63231b94edd43a43e02 linux-3.0/MAINTAINERS
99cc9f0dfd86e63231b94edd43a43e02 linux-3.0/MAINTAINERS
strace shows:
Bad
---
fstat(3, {st_mode=S_IFREG|0644, st_size=196745, ...}) = 0
read(3, "\n\tList of maintainers and how to"..., 32768) = 32768
read(3, "M:\tSylwester Nawrocki <s.nawrock"..., 32768) = 32768
read(3, "rs/scsi/eata*\n\nEATA ISA/EISA/PCI"..., 32768) = 32768
read(3, "F:\tDocumentation/isapnp.txt\nF:\td"..., 32768) = 32768
read(3, "hunkeey@googlemail.com>\nL:\tlinux"..., 32768) = 32768
2021-06-26 16:09:04 +02:00
read(3, "ach-spear3xx/\n\nSPEAR6XX MACHINE "..., 32768) = 32768 <--- WRONG LENGTH!!!
read(3, "", 32768) = 0
2021-06-26 16:09:04 +02:00
lseek(3, 0, SEEK_CUR) = 196608 <--- WRONG LENGTH!!!
close(3) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...}) = 0
write(1, "279b6ab0491e7532132e8f32afe6c04d"..., 56279b6ab0491e7532132e8f32afe6c04d linux-3.0/MAINTAINERS
Good
----
fstat(3, {st_mode=S_IFREG|0644, st_size=195191, ...}) = 0
read(3, "\n\tList of maintainers and how to"..., 32768) = 32768
read(3, "M:\tSylwester Nawrocki <s.nawrock"..., 32768) = 32768
read(3, "rs/scsi/eata*\n\nEATA ISA/EISA/PCI"..., 32768) = 32768
read(3, "F:\tDocumentation/isapnp.txt\nF:\td"..., 32768) = 32768
read(3, "hunkeey@googlemail.com>\nL:\tlinux"..., 32768) = 32768
read(3, "ach-spear3xx/\n\nSPEAR6XX MACHINE "..., 32768) = 31351
read(3, "", 4096) = 0
lseek(3, 0, SEEK_CUR) = 195191
close(3) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...}) = 0
write(1, "99cc9f0dfd86e63231b94edd43a43e02"..., 5699cc9f0dfd86e63231b94edd43a43e02 linux-3.0/MAINTAINERS
*/
func TestMd5sumMaintainers(t *testing.T) {
fn := filepath.Join(test_helpers.DefaultPlainDir, t.Name())
f, err := os.Create(fn)
if err != nil {
t.Fatal(err)
}
// Size of the MAINTAINERS file = 195191
const sizeWant = 195191
content := make([]byte, sizeWant)
_, err = f.Write(content)
if err != nil {
t.Fatal(err)
}
f.Close()
// Remount to clear the linux kernel attr cache
// (otherwise we would have to wait 2 seconds for the entry to expire)
test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, "-zerokey")
cmd := exec.Command("md5sum", fn, fn, fn, fn)
out2, err := cmd.CombinedOutput()
Fix issues found by ineffassign gocryptfs$ ineffassign ./... /home/jakob/go/src/github.com/rfjakob/gocryptfs/internal/configfile/config_file.go:243:2: ineffectual assignment to scryptHash /home/jakob/go/src/github.com/rfjakob/gocryptfs/internal/configfile/config_file.go:272:2: ineffectual assignment to scryptHash /home/jakob/go/src/github.com/rfjakob/gocryptfs/internal/fusefrontend/file.go:285:3: ineffectual assignment to fileID /home/jakob/go/src/github.com/rfjakob/gocryptfs/internal/fusefrontend/node.go:367:3: ineffectual assignment to err /home/jakob/go/src/github.com/rfjakob/gocryptfs/internal/fusefrontend/node_open_create.go:68:2: ineffectual assignment to fd /home/jakob/go/src/github.com/rfjakob/gocryptfs/mount.go:308:2: ineffectual assignment to masterkey /home/jakob/go/src/github.com/rfjakob/gocryptfs/gocryptfs-xray/xray_main.go:156:13: ineffectual assignment to err /home/jakob/go/src/github.com/rfjakob/gocryptfs/internal/fusefrontend/prepare_syscall_test.go:65:16: ineffectual assignment to errno /home/jakob/go/src/github.com/rfjakob/gocryptfs/internal/syscallcompat/open_nofollow_test.go:34:2: ineffectual assignment to fd /home/jakob/go/src/github.com/rfjakob/gocryptfs/tests/defaults/acl_test.go:111:6: ineffectual assignment to err /home/jakob/go/src/github.com/rfjakob/gocryptfs/tests/defaults/acl_test.go:181:2: ineffectual assignment to sz /home/jakob/go/src/github.com/rfjakob/gocryptfs/tests/defaults/acl_test.go:198:2: ineffectual assignment to sz /home/jakob/go/src/github.com/rfjakob/gocryptfs/tests/defaults/main_test.go:365:8: ineffectual assignment to err /home/jakob/go/src/github.com/rfjakob/gocryptfs/tests/xattr/xattr_fd_test.go:30:6: ineffectual assignment to err /home/jakob/go/src/github.com/rfjakob/gocryptfs/tests/xattr/xattr_fd_test.go:66:6: ineffectual assignment to err
2021-08-18 15:47:17 +02:00
if err != nil {
t.Fatal(err)
}
out := string(out2)
// 195191 zero bytes have this md5sum
const md5Want = "b99bf6917f688068acd49126f3b1b005"
n := strings.Count(out, md5Want)
if n != 4 {
t.Errorf("found %d instead of %d instances of %q", n, 4, md5Want)
t.Logf("full output:\n%s", out)
}
}
2021-06-26 16:09:04 +02:00
func TestMaxlen(t *testing.T) {
workDir := filepath.Join(test_helpers.DefaultPlainDir, t.Name())
if err := os.Mkdir(workDir, 0700); err != nil {
t.Fatal(err)
}
cmd := exec.Command("../../contrib/maxlen.bash", workDir)
cmd.Env = []string{"QUICK=1"}
2021-06-26 16:09:04 +02:00
out, err := cmd.CombinedOutput()
if err != nil {
t.Log(string(out))
t.Fatal(err)
}
want := `
Maximum filename length: 255
Maximum path length with 100 chars per subdir: 4095
`
if !strings.HasSuffix(string(out), want) {
t.Errorf("wrong output: %s", string(out))
}
}
func TestFsync(t *testing.T) {
fileName := test_helpers.DefaultPlainDir + "/" + t.Name() + ".file"
fileFD, err := syscall.Open(fileName, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_TRUNC, 0600)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fileFD)
dirName := test_helpers.DefaultPlainDir + "/" + t.Name() + ".dir"
if err := os.Mkdir(dirName, 0700); err != nil {
t.Fatal(err)
}
dirFD, err := syscall.Open(dirName, syscall.O_RDONLY, 0)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(dirFD)
err = syscall.Fsync(dirFD)
if err != nil {
t.Fatal(err)
}
err = syscall.Fsync(fileFD)
if err != nil {
t.Fatal(err)
}
}
// force_owner was broken by the v2.0 rewrite:
// The owner was only forced for GETATTR, but not for CREATE or LOOKUP.
//
// https://github.com/rfjakob/gocryptfs/issues/609
// https://github.com/rfjakob/gocryptfs/pull/610
func TestForceOwner(t *testing.T) {
cDir := test_helpers.InitFS(t)
os.Chmod(cDir, 0777) // Mount needs to be accessible for us
pDir := cDir + ".mnt"
test_helpers.MountOrFatal(t, cDir, pDir, "-force_owner=1234:1234", "-extpass=echo test")
defer test_helpers.UnmountPanic(pDir)
// We need an unrestricted umask
oldmask := syscall.Umask(0)
defer syscall.Umask(oldmask)
foo := pDir + "/foo"
// In the answer to a FUSE CREATE, gocryptfs sends file information including
// the owner. This is cached by the kernel and will be used for the next
// stat() call.
fd, err := syscall.Open(foo, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_EXCL, 0666)
if err != nil {
t.Fatal(err)
}
syscall.Close(fd)
var st syscall.Stat_t
if err := syscall.Stat(foo, &st); err != nil {
t.Fatal(err)
}
if st.Uid != 1234 || st.Gid != 1234 {
t.Errorf("CREATE returned uid or gid != 1234: %#v", st)
}
// We can clear the kernel stat() cache by writing to the file
fd, err = syscall.Open(foo, syscall.O_WRONLY, 0)
if err != nil {
t.Fatal(err)
}
if _, err := syscall.Write(fd, []byte("hello world")); err != nil {
t.Fatal(err)
}
syscall.Close(fd)
// This stat() triggers a new GETATTR
if err := syscall.Stat(foo, &st); err != nil {
t.Fatal(err)
}
if st.Uid != 1234 || st.Gid != 1234 {
t.Errorf("GETATTR returned uid or gid != 1234: %#v", st)
}
// Remount to clear cache
test_helpers.UnmountPanic(pDir)
test_helpers.MountOrFatal(t, cDir, pDir, "-force_owner=1234:1234", "-extpass=echo test")
// This stat() triggers a new LOOKUP
if err := syscall.Stat(foo, &st); err != nil {
t.Fatal(err)
}
if st.Uid != 1234 || st.Gid != 1234 {
t.Errorf("LOOKUP returned uid or gid != 1234: %#v", st)
}
}