Compare commits

...

17 Commits

Author SHA1 Message Date
Jakob Unterwurzacher a2b54cfccd root_test: fix leftover loop mount
After running "make root_test" a few times df would look like this,
no good:

$ df
Filesystem                  1K-blocks       Used Available Use% Mounted on
[...]
/dev/loop11                      8729       8525         0 100% /tmp/gocryptfs-test-parent-0/4081611019/TestDiskFull.ext4.mnt
/dev/loop12                      8729       8525         0 100% /tmp/gocryptfs-test-parent-0/1959939106/TestDiskFull.ext4.mnt
/dev/loop13                      8729       8525         0 100% /tmp/gocryptfs-test-parent-0/2455888382/TestDiskFull.ext4.mnt
/dev/loop14                      8729       8525         0 100% /tmp/gocryptfs-test-parent-0/2002998275/TestDiskFull.ext4.mnt
/dev/loop15                      8729       8525         0 100% /var/tmp/gocryptfs-test-parent-0/806736609/TestDiskFull.ext4.mnt
/dev/loop16                      8729       8525         0 100% /tmp/gocryptfs-test-parent-0/4050106930/TestDiskFull.ext4.mnt
/dev/loop17                      8729       8525         0 100% /tmp/gocryptfs-test-parent-0/1661931756/TestDiskFull.ext4.mnt
/dev/loop18                      8729       8525         0 100% /tmp/gocryptfs-test-parent-0/617990718/TestDiskFull.ext4.mnt
/dev/loop19                      8729       8525         0 100% /tmp/gocryptfs-test-parent-0/3194420338/TestDiskFull.ext4.mnt
/dev/loop20                      8729       8525         0 100% /tmp/gocryptfs-test-parent-0/2180745159/TestDiskFull.ext4.mnt

Turns out the unmount failed with EBUSY, so use lazy
unmount.
2022-01-27 18:36:51 +01:00
Jakob Unterwurzacher ba75aa1ab0 root_test: add TestOverlay ; syscallcompat: add QuirkNoUserXattr 2022-01-27 15:44:09 +01:00
Jakob Unterwurzacher b636f79f89 MANPAGE: add missing -acl section
Looks like 86d8336b43
forgot to add the option to the manpage.
2022-01-22 16:19:33 +01:00
Jakob Unterwurzacher 47358938ec tests: fix build failure on Go 1.15 and older
These don't have os.ReadDir yet.

Error was:

	Error: vet: tests/defaults/overlayfs_test.go:104:15: ReadDir not declared by package os
2022-01-22 16:07:59 +01:00
Jakob Unterwurzacher 696f11499b tests: add skipped O_TMPFILE test
Looks like the FUSE protocol does support O_TMPFILE yet.

https://github.com/rfjakob/gocryptfs/issues/641
2022-01-22 14:06:39 +01:00
Jakob Unterwurzacher b859bc96ef fusefrontend: fix "duplicate case" darwin build failure
$ ./crossbuild.bash
[...]
+ GOOS=darwin
+ GOARCH=amd64
+ build
+ go build -tags without_openssl -o /dev/null
internal/fusefrontend/node.go:397:2: duplicate case syscallcompat.RENAME_NOREPLACE (value 0) in switch
	previous case at internal/fusefrontend/node.go:397:7
internal/fusefrontend/node.go:397:2: duplicate case syscallcompat.RENAME_EXCHANGE (value 0) in switch
	previous case at internal/fusefrontend/node.go:397:7
internal/fusefrontend/node.go:397:2: duplicate case syscallcompat.RENAME_WHITEOUT (value 0) in switch
	previous case at internal/fusefrontend/node.go:397:7
internal/fusefrontend/node.go:399:38: duplicate case syscallcompat.RENAME_NOREPLACE | syscallcompat.RENAME_WHITEOUT (value 0) in switch
	previous case at internal/fusefrontend/node.go:397:7
2022-01-22 12:44:04 +01:00
Jakob Unterwurzacher 3bac814ea9 tests: add TestRenameWhiteout, TestRenameExchange
f
2022-01-22 12:42:57 +01:00
Jakob Unterwurzacher b7cac4ffd0 fusefrontend: support RENAME_WHITEOUT, RENAME_EXCHANGE
Both new internal test and xfstests generic/013 are happy.

https://github.com/rfjakob/gocryptfs/issues/641
2022-01-22 12:28:27 +01:00
Jakob Unterwurzacher 3ca2b1983d tests: enable -fusedebug if FUSEDEBUG env is set 2022-01-22 11:46:08 +01:00
Jakob Unterwurzacher 5f955423b7 fusefrontend: fix -force_owner not affecting MKNOD
Fixes https://github.com/rfjakob/gocryptfs/issues/629
2022-01-10 20:05:36 +01:00
Jakob Unterwurzacher c23a7f2259 test.bash: disable parallelism in verbose mode
This way we get live output, and hopefully see clearer
where things hang if they do.

Also, don't pass on flags to "go vet", the verbose output
is pretty useless.

https://github.com/rfjakob/gocryptfs/issues/625
2022-01-04 15:25:26 +01:00
Jakob Unterwurzacher 700ae685cc tests: improve SEEK_DATA test for MacOS
(1)

Create a 1 GiB file instead of 1 TiB, because
apparently, on MacOS, the file (sometimes?) is not
created sparse, and fills up users' disks:

https://github.com/rfjakob/gocryptfs/issues/625

(2)

On darwin, SEEK_DATA is not the same as on Linux
( 2f8b555de2 )
so use the value provided by the unix package.
2022-01-04 15:21:20 +01:00
Jakob Unterwurzacher 4b251f3ce1 readpassword: bubble up errors instead of exiting the process
This allows cleanups to happen in the caller, like removing
the control socket.

Fixes https://github.com/rfjakob/gocryptfs/issues/634
2022-01-03 15:18:59 +01:00
Jakob Unterwurzacher 1eaf1211a2 tests/cli: Check for leftover socket file
This fails at the moment:

$ go test ./tests/cli/
--- FAIL: TestMountPasswordEmpty (0.01s)
    cli_test.go:430: socket file "/tmp/gocryptfs-test-parent-1026/3413782690/TestMountPasswordEmpty.753166857.sock" left behind

https://github.com/rfjakob/gocryptfs/issues/634
2022-01-03 14:27:52 +01:00
Jakob Unterwurzacher 5749e70c7c nametransform: fix oversight in comment 2021-12-19 14:50:52 +01:00
Jakob Unterwurzacher 8d8b76dcac go.mod: upgrade go-fuse
We want
https://github.com/hanwen/go-fuse/commit/934a183ed91446d218b5471c4df9f93db039f6e
"fuse: prefer fusermount3 over fusermount; add debug output"

Fixes https://github.com/rfjakob/gocryptfs/issues/626
2021-12-19 14:46:43 +01:00
Jakob Unterwurzacher 64be5de75f fusefrontend: allow slashes in xattr names
xattr names have fewer restrictions than file names,
relax the validation.

Fixes https://github.com/rfjakob/gocryptfs/issues/627
2021-12-19 14:43:56 +01:00
30 changed files with 567 additions and 211 deletions

View File

@ -177,6 +177,10 @@ MOUNT OPTIONS
Available options for mounting are listed below. Usually, you don't need any.
Defaults are fine.
#### -acl
Enable ACL enforcement. When you want to use ACLs, you must enable this
option.
#### -allow_other
By default, the Linux kernel prevents any other user (even root) to
access a mounted FUSE filesystem. Settings this option allows access for

2
go.mod
View File

@ -3,7 +3,7 @@ module github.com/rfjakob/gocryptfs/v2
go 1.16
require (
github.com/hanwen/go-fuse/v2 v2.1.1-0.20210825171523-3ab5d95a30ae
github.com/hanwen/go-fuse/v2 v2.1.1-0.20211219085202-934a183ed914
github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115
github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd // indirect
github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff // indirect

2
go.sum
View File

@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hanwen/go-fuse/v2 v2.1.1-0.20210825171523-3ab5d95a30ae h1:4CB6T4YTUVvnro5ba8ju1QCbOuyGAeF3vvKlo50EJ4k=
github.com/hanwen/go-fuse/v2 v2.1.1-0.20210825171523-3ab5d95a30ae/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc=
github.com/hanwen/go-fuse/v2 v2.1.1-0.20211219085202-934a183ed914 h1:hGXMxS1wTE4y+f7iBqFArrJ6X8QozHnEdnVzGZI9Ywc=
github.com/hanwen/go-fuse/v2 v2.1.1-0.20211219085202-934a183ed914/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc=
github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115 h1:YuDUUFNM21CAbyPOpOP8BicaTD/0klJEKt5p8yuw+uY=
github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115/go.mod h1:LadVJg0XuawGk+8L1rYnIED8451UyNxEMdTWCEt5kmU=
github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA=

View File

@ -148,7 +148,11 @@ func dumpMasterKey(fn string, fido2Path string) {
}
pw = fido2.Secret(fido2Path, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
} else {
pw = readpassword.Once(nil, nil, "")
pw, err = readpassword.Once(nil, nil, "")
if err != nil {
tlog.Fatal.Println(err)
os.Exit(exitcodes.ReadPassword)
}
}
masterkey, err := cf.DecryptMasterKey(pw)
// Purge password from memory

View File

@ -87,7 +87,11 @@ func initDir(args *argContainer) {
password = fido2.Secret(args.fido2, fido2CredentialID, fido2HmacSalt)
} else {
// normal password entry
password = readpassword.Twice([]string(args.extpass), []string(args.passfile))
password, err = readpassword.Twice([]string(args.extpass), []string(args.passfile))
if err != nil {
tlog.Fatal.Println(err)
os.Exit(exitcodes.ReadPassword)
}
fido2CredentialID = nil
fido2HmacSalt = nil
}

View File

@ -277,7 +277,13 @@ func (n *Node) Mknod(ctx context.Context, name string, mode, rdev uint32, out *f
errno = fs.ToErrno(err)
return
}
inode = n.newChild(ctx, st, out)
if rn.args.ForceOwner != nil {
out.Owner = *rn.args.ForceOwner
}
return inode, 0
}
@ -385,23 +391,17 @@ func (n *Node) Symlink(ctx context.Context, target, name string, out *fuse.Entry
return inode, 0
}
// xfstests generic/013 now also exercises RENAME_EXCHANGE and RENAME_WHITEOUT,
// uncovering lots of problems with longnames
//
// Reject those flags with syscall.EINVAL.
// If we can handle the flags, this function returns 0.
func rejectRenameFlags(flags uint32) syscall.Errno {
// Normal rename, we can handle that
if flags == 0 {
switch flags {
case 0, syscallcompat.RENAME_NOREPLACE, syscallcompat.RENAME_EXCHANGE, syscallcompat.RENAME_WHITEOUT:
return 0
}
// We also can handle RENAME_NOREPLACE
if flags == syscallcompat.RENAME_NOREPLACE {
case syscallcompat.RENAME_NOREPLACE | syscallcompat.RENAME_WHITEOUT:
return 0
default:
tlog.Warn.Printf("rejectRenameFlags: unknown flag combination 0x%x", flags)
return syscall.EINVAL
}
// We cannot handle RENAME_EXCHANGE and RENAME_WHITEOUT yet.
// Needs extra code for .name files.
return syscall.EINVAL
}
// Rename - FUSE call.
@ -466,6 +466,11 @@ func (n *Node) Rename(ctx context.Context, name string, newParent fs.InodeEmbedd
}
return fs.ToErrno(err)
}
if flags&syscallcompat.RENAME_EXCHANGE != 0 || flags&syscallcompat.RENAME_WHITEOUT != 0 {
// These flags mean that there is now a new file at cName and we
// should NOT delete its longname file.
return 0
}
if nametransform.IsLongContent(cName) {
nametransform.DeleteLongNameAt(dirfd, cName)
}

View File

@ -15,10 +15,6 @@ import (
// -1 as uint32
const minus1 = ^uint32(0)
// xattr names are encrypted like file names, but with a fixed IV.
// Padded with "_xx" for length 16.
var xattrNameIV = []byte("xattr_name_iv_xx")
// We store encrypted xattrs under this prefix plus the base64-encoded
// encrypted original name.
var xattrStorePrefix = "user.gocryptfs."

View File

@ -268,7 +268,7 @@ func (rn *RootNode) decryptXattrValue(cData []byte) (data []byte, err error) {
// encryptXattrName transforms "user.foo" to "user.gocryptfs.a5sAd4XAa47f5as6dAf"
func (rn *RootNode) encryptXattrName(attr string) (string, error) {
// xattr names are encrypted like file names, but with a fixed IV.
cAttr, err := rn.nameTransform.EncryptName(attr, xattrNameIV)
cAttr, err := rn.nameTransform.EncryptXattrName(attr)
if err != nil {
return "", err
}
@ -282,7 +282,7 @@ func (rn *RootNode) decryptXattrName(cAttr string) (attr string, err error) {
}
// Strip "user.gocryptfs." prefix
cAttr = cAttr[len(xattrStorePrefix):]
attr, err = rn.nameTransform.DecryptName(cAttr, xattrNameIV)
attr, err = rn.nameTransform.DecryptXattrName(cAttr)
if err != nil {
return "", err
}

View File

@ -66,7 +66,14 @@ func New(e *eme.EMECipher, longNames bool, longNameMax uint8, raw64 bool, badnam
func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) {
res, err := n.decryptName(cipherName, iv)
if err != nil && n.HaveBadnamePatterns() {
return n.decryptBadname(cipherName, iv)
res, err = n.decryptBadname(cipherName, iv)
}
if err != nil {
return "", err
}
if err := IsValidName(res); err != nil {
tlog.Warn.Printf("DecryptName %q: invalid name after decryption: %v", cipherName, err)
return "", syscall.EBADMSG
}
return res, err
}
@ -79,30 +86,29 @@ func (n *NameTransform) decryptName(cipherName string, iv []byte) (string, error
return "", err
}
if len(bin) == 0 {
tlog.Warn.Printf("DecryptName: empty input")
tlog.Warn.Printf("decryptName: empty input")
return "", syscall.EBADMSG
}
if len(bin)%aes.BlockSize != 0 {
tlog.Debug.Printf("DecryptName %q: decoded length %d is not a multiple of 16", cipherName, len(bin))
tlog.Debug.Printf("decryptName %q: decoded length %d is not a multiple of 16", cipherName, len(bin))
return "", syscall.EBADMSG
}
bin = n.emeCipher.Decrypt(iv, bin)
bin, err = unPad16(bin)
if err != nil {
tlog.Warn.Printf("DecryptName %q: unPad16 error: %v", cipherName, err)
tlog.Warn.Printf("decryptName %q: unPad16 error: %v", cipherName, err)
return "", syscall.EBADMSG
}
plain := string(bin)
if err := IsValidName(plain); err != nil {
tlog.Warn.Printf("DecryptName %q: invalid name after decryption: %v", cipherName, err)
return "", syscall.EBADMSG
}
return plain, err
}
// EncryptName encrypts "plainName", returns a base64-encoded "cipherName64",
// EncryptName encrypts a file name "plainName" and returns a base64-encoded "cipherName64",
// encrypted using EME (https://github.com/rfjakob/eme).
//
// plainName is checked for null bytes, slashes etc. and such names are rejected
// with an error.
//
// This function is exported because in some cases, fusefrontend needs access
// to the full (not hashed) name if longname is used.
func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 string, err error) {
@ -110,11 +116,19 @@ func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 s
tlog.Warn.Printf("EncryptName %q: invalid plainName: %v", plainName, err)
return "", syscall.EBADMSG
}
return n.encryptName(plainName, iv), nil
}
// encryptName encrypts "plainName" and returns a base64-encoded "cipherName64",
// encrypted using EME (https://github.com/rfjakob/eme).
//
// No checks for null bytes etc are performed against plainName.
func (n *NameTransform) encryptName(plainName string, iv []byte) (cipherName64 string) {
bin := []byte(plainName)
bin = pad16(bin)
bin = n.emeCipher.Encrypt(iv, bin)
cipherName64 = n.B64.EncodeToString(bin)
return cipherName64, nil
return cipherName64
}
// EncryptAndHashName encrypts "name" and hashes it to a longname if it is

View File

@ -75,3 +75,26 @@ func TestIsValidName(t *testing.T) {
}
}
}
func TestIsValidXattrName(t *testing.T) {
cases := []struct {
in string
want bool
}{
{"", false},
{".", true},
{"..", true},
{"...", true},
{"asdasd/asdasd", true},
{"asdasd\000asdasd", false},
{"hello", true},
{strings.Repeat("x", 255), true},
{strings.Repeat("x", 256), true},
}
for _, c := range cases {
have := isValidXattrName(c.in)
if (have == nil) != c.want {
t.Errorf("isValidXattrName(%q): want %v have %v", c.in, c.want, have)
}
}
}

View File

@ -0,0 +1,47 @@
package nametransform
import (
"fmt"
"strings"
"syscall"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)
// xattr names are encrypted like file names, but with a fixed IV.
// Padded with "_xx" for length 16.
var xattrNameIV = []byte("xattr_name_iv_xx")
func isValidXattrName(name string) error {
if name == "" {
return fmt.Errorf("empty input")
}
if strings.Contains(name, "\000") {
return fmt.Errorf("contains forbidden null byte")
}
return nil
}
// EncryptXattrName encrypts an extended attribute (xattr) name.
// xattr names are encrypted like file names, but with a fixed IV, and fewer
// naming restriction.
func (n *NameTransform) EncryptXattrName(plainName string) (cipherName64 string, err error) {
if err := isValidXattrName(plainName); err != nil {
tlog.Warn.Printf("EncryptXattrName %q: invalid plainName: %v", plainName, err)
return "", syscall.EBADMSG
}
return n.encryptName(plainName, xattrNameIV), nil
}
// DecryptXattrName calls decryptName to try and decrypt a base64-encoded encrypted
// filename "cipherName", and failing that checks if it can be bypassed
func (n *NameTransform) DecryptXattrName(cipherName string) (plainName string, err error) {
if plainName, err = n.decryptName(cipherName, xattrNameIV); err != nil {
return "", err
}
if err := isValidXattrName(plainName); err != nil {
tlog.Warn.Printf("DecryptXattrName %q: invalid name after decryption: %v", cipherName, err)
return "", syscall.EBADMSG
}
return plainName, err
}

View File

@ -2,7 +2,6 @@ package readpassword
import (
"os"
"os/exec"
"testing"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
@ -16,68 +15,76 @@ func TestMain(m *testing.M) {
func TestExtpass(t *testing.T) {
p1 := "ads2q4tw41reg52"
p2 := string(readPasswordExtpass([]string{"echo " + p1}))
if p1 != p2 {
t.Errorf("p1=%q != p2=%q", p1, p2)
p2, err := readPasswordExtpass([]string{"echo " + p1})
if err != nil {
t.Fatal(err)
}
if p1 != string(p2) {
t.Errorf("p1=%q != p2=%q", p1, string(p2))
}
}
func TestOnceExtpass(t *testing.T) {
p1 := "lkadsf0923rdfi48rqwhdsf"
p2 := string(Once([]string{"echo " + p1}, nil, ""))
if p1 != p2 {
t.Errorf("p1=%q != p2=%q", p1, p2)
p2, err := Once([]string{"echo " + p1}, nil, "")
if err != nil {
t.Fatal(err)
}
if p1 != string(p2) {
t.Errorf("p1=%q != p2=%q", p1, string(p2))
}
}
// extpass with two arguments
func TestOnceExtpass2(t *testing.T) {
p1 := "foo"
p2 := string(Once([]string{"echo", p1}, nil, ""))
if p1 != p2 {
t.Errorf("p1=%q != p2=%q", p1, p2)
p2, err := Once([]string{"echo", p1}, nil, "")
if err != nil {
t.Fatal(err)
}
if p1 != string(p2) {
t.Errorf("p1=%q != p2=%q", p1, string(p2))
}
}
// extpass with three arguments
func TestOnceExtpass3(t *testing.T) {
p1 := "foo bar baz"
p2 := string(Once([]string{"echo", "foo", "bar", "baz"}, nil, ""))
if p1 != p2 {
t.Errorf("p1=%q != p2=%q", p1, p2)
p2, err := Once([]string{"echo", "foo", "bar", "baz"}, nil, "")
if err != nil {
t.Fatal(err)
}
if p1 != string(p2) {
t.Errorf("p1=%q != p2=%q", p1, string(p2))
}
}
func TestOnceExtpassSpaces(t *testing.T) {
p1 := "mypassword"
p2 := string(Once([]string{"cat", "passfile_test_files/file with spaces.txt"}, nil, ""))
if p1 != p2 {
t.Errorf("p1=%q != p2=%q", p1, p2)
p2, err := Once([]string{"cat", "passfile_test_files/file with spaces.txt"}, nil, "")
if err != nil {
t.Fatal(err)
}
if p1 != string(p2) {
t.Errorf("p1=%q != p2=%q", p1, string(p2))
}
}
func TestTwiceExtpass(t *testing.T) {
p1 := "w5w44t3wfe45srz434"
p2 := string(Once([]string{"echo " + p1}, nil, ""))
if p1 != p2 {
t.Errorf("p1=%q != p2=%q", p1, p2)
p2, err := Once([]string{"echo " + p1}, nil, "")
if err != nil {
t.Fatal(err)
}
if p1 != string(p2) {
t.Errorf("p1=%q != p2=%q", p1, string(p2))
}
}
// When extpass returns an empty string, we should crash.
//
// The TEST_SLAVE magic is explained at
// https://talks.golang.org/2014/testing.slide#23 .
// Empty extpass should fail
func TestExtpassEmpty(t *testing.T) {
if os.Getenv("TEST_SLAVE") == "1" {
readPasswordExtpass([]string{"echo"})
return
_, err := readPasswordExtpass([]string{"echo"})
if err == nil {
t.Fatal("empty password should have failed")
}
cmd := exec.Command(os.Args[0], "-test.run=TestExtpassEmpty$")
cmd.Env = append(os.Environ(), "TEST_SLAVE=1")
err := cmd.Run()
if err != nil {
return
}
t.Fatal("empty password should have failed")
}

View File

@ -2,28 +2,31 @@ package readpassword
import (
"bytes"
"fmt"
"os"
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)
// readPassFileConcatenate reads the first line from each file name and
// concatenates the results. The result does not contain any newlines.
func readPassFileConcatenate(passfileSlice []string) (result []byte) {
func readPassFileConcatenate(passfileSlice []string) (result []byte, err error) {
for _, e := range passfileSlice {
result = append(result, readPassFile(e)...)
add, err := readPassFile(e)
if err != nil {
return nil, err
}
result = append(result, add...)
}
return result
return result, nil
}
// readPassFile reads the first line from the passed file name.
func readPassFile(passfile string) []byte {
func readPassFile(passfile string) ([]byte, error) {
tlog.Info.Printf("passfile: reading from file %q", passfile)
f, err := os.Open(passfile)
if err != nil {
tlog.Fatal.Printf("fatal: passfile: could not open %q: %v", passfile, err)
os.Exit(exitcodes.ReadPassword)
return nil, fmt.Errorf("fatal: passfile: could not open %q: %v", passfile, err)
}
defer f.Close()
// +1 for an optional trailing newline,
@ -31,23 +34,20 @@ func readPassFile(passfile string) []byte {
buf := make([]byte, maxPasswordLen+2)
n, err := f.Read(buf)
if err != nil {
tlog.Fatal.Printf("fatal: passfile: could not read from %q: %v", passfile, err)
os.Exit(exitcodes.ReadPassword)
return nil, fmt.Errorf("fatal: passfile: could not read from %q: %v", passfile, err)
}
buf = buf[:n]
// Split into first line and "trailing garbage"
lines := bytes.SplitN(buf, []byte("\n"), 2)
if len(lines[0]) == 0 {
tlog.Fatal.Printf("fatal: passfile: empty first line in %q", passfile)
os.Exit(exitcodes.ReadPassword)
return nil, fmt.Errorf("fatal: passfile: empty first line in %q", passfile)
}
if len(lines[0]) > maxPasswordLen {
tlog.Fatal.Printf("fatal: passfile: max password length (%d bytes) exceeded", maxPasswordLen)
os.Exit(exitcodes.ReadPassword)
return nil, fmt.Errorf("fatal: passfile: max password length (%d bytes) exceeded", maxPasswordLen)
}
if len(lines) > 1 && len(lines[1]) > 0 {
tlog.Warn.Printf("warning: passfile: ignoring trailing garbage (%d bytes) after first line",
len(lines[1]))
}
return lines[0]
return lines[0], nil
}

View File

@ -1,8 +1,6 @@
package readpassword
import (
"os"
"os/exec"
"testing"
)
@ -17,76 +15,49 @@ func TestPassfile(t *testing.T) {
{"file with spaces.txt", "mypassword"},
}
for _, tc := range testcases {
pw := readPassFile("passfile_test_files/" + tc.file)
pw, err := readPassFile("passfile_test_files/" + tc.file)
if err != nil {
t.Fatal(err)
}
if string(pw) != tc.want {
t.Errorf("Wrong result: want=%q have=%q", tc.want, pw)
}
// Calling readPassFileConcatenate with only one element should give the
// same result
pw = readPassFileConcatenate([]string{"passfile_test_files/" + tc.file})
pw, err = readPassFileConcatenate([]string{"passfile_test_files/" + tc.file})
if err != nil {
t.Fatal(err)
}
if string(pw) != tc.want {
t.Errorf("Wrong result: want=%q have=%q", tc.want, pw)
}
}
}
// readPassFile() should exit instead of returning an empty string.
//
// The TEST_SLAVE magic is explained at
// https://talks.golang.org/2014/testing.slide#23 , mirror:
// http://web.archive.org/web/20200426174352/https://talks.golang.org/2014/testing.slide#23
// readPassFile() should fail instead of returning an empty string.
func TestPassfileEmpty(t *testing.T) {
if os.Getenv("TEST_SLAVE") == "1" {
readPassFile("passfile_test_files/empty.txt")
return
_, err := readPassFile("passfile_test_files/empty.txt")
if err == nil {
t.Fatal("should have failed")
}
cmd := exec.Command(os.Args[0], "-test.run=TestPassfileEmpty$")
cmd.Env = append(os.Environ(), "TEST_SLAVE=1")
err := cmd.Run()
if err != nil {
return
}
t.Fatal("should have exited")
}
// File containing just a newline.
// readPassFile() should exit instead of returning an empty string.
//
// The TEST_SLAVE magic is explained at
// https://talks.golang.org/2014/testing.slide#23 , mirror:
// http://web.archive.org/web/20200426174352/https://talks.golang.org/2014/testing.slide#23
// readPassFile() should fal instead of returning an empty string.
func TestPassfileNewline(t *testing.T) {
if os.Getenv("TEST_SLAVE") == "1" {
readPassFile("passfile_test_files/newline.txt")
return
_, err := readPassFile("passfile_test_files/newline.txt")
if err == nil {
t.Fatal("should have failed")
}
cmd := exec.Command(os.Args[0], "-test.run=TestPassfileNewline$")
cmd.Env = append(os.Environ(), "TEST_SLAVE=1")
err := cmd.Run()
if err != nil {
return
}
t.Fatal("should have exited")
}
// File containing "\ngarbage".
// readPassFile() should exit instead of returning an empty string.
//
// The TEST_SLAVE magic is explained at
// https://talks.golang.org/2014/testing.slide#23 , mirror:
// http://web.archive.org/web/20200426174352/https://talks.golang.org/2014/testing.slide#23
// readPassFile() should return an error.
func TestPassfileEmptyFirstLine(t *testing.T) {
if os.Getenv("TEST_SLAVE") == "1" {
readPassFile("passfile_test_files/empty_first_line.txt")
return
_, err := readPassFile("passfile_test_files/empty_first_line.txt")
if err == nil {
t.Fatal("should have failed")
}
cmd := exec.Command(os.Args[0], "-test.run=TestPassfileEmptyFirstLine$")
cmd.Env = append(os.Environ(), "TEST_SLAVE=1")
err := cmd.Run()
if err != nil {
return
}
t.Fatal("should have exited")
}
// TestPassFileConcatenate tests readPassFileConcatenate
@ -95,8 +66,11 @@ func TestPassFileConcatenate(t *testing.T) {
"passfile_test_files/file with spaces.txt",
"passfile_test_files/mypassword_garbage.txt",
}
res := string(readPassFileConcatenate(files))
if res != "mypasswordmypassword" {
res, err := readPassFileConcatenate(files)
if err != nil {
t.Fatal(err)
}
if string(res) != "mypasswordmypassword" {
t.Errorf("wrong result: %q", res)
}
}

View File

@ -11,7 +11,6 @@ import (
"golang.org/x/crypto/ssh/terminal"
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)
@ -22,7 +21,7 @@ const (
// Once tries to get a password from the user, either from the terminal, extpass, passfile
// or stdin. Leave "prompt" empty to use the default "Password: " prompt.
func Once(extpass []string, passfile []string, prompt string) []byte {
func Once(extpass []string, passfile []string, prompt string) ([]byte, error) {
if len(passfile) != 0 {
return readPassFileConcatenate(passfile)
}
@ -40,7 +39,7 @@ func Once(extpass []string, passfile []string, prompt string) []byte {
// Twice is the same as Once but will prompt twice if we get the password from
// the terminal.
func Twice(extpass []string, passfile []string) []byte {
func Twice(extpass []string, passfile []string) ([]byte, error) {
if len(passfile) != 0 {
return readPassFileConcatenate(passfile)
}
@ -50,54 +49,59 @@ func Twice(extpass []string, passfile []string) []byte {
if !terminal.IsTerminal(int(os.Stdin.Fd())) {
return readPasswordStdin("Password")
}
p1 := readPasswordTerminal("Password: ")
p2 := readPasswordTerminal("Repeat: ")
p1, err := readPasswordTerminal("Password: ")
if err != nil {
return nil, err
}
p2, err := readPasswordTerminal("Repeat: ")
if err != nil {
return nil, err
}
if !bytes.Equal(p1, p2) {
tlog.Fatal.Println("Passwords do not match")
os.Exit(exitcodes.ReadPassword)
return nil, fmt.Errorf("Passwords do not match")
}
// Wipe the password duplicate from memory
for i := range p2 {
p2[i] = 0
}
return p1
return p1, nil
}
// readPasswordTerminal reads a line from the terminal.
// Exits on read error or empty result.
func readPasswordTerminal(prompt string) []byte {
func readPasswordTerminal(prompt string) ([]byte, error) {
fd := int(os.Stdin.Fd())
fmt.Fprintf(os.Stderr, prompt)
// terminal.ReadPassword removes the trailing newline
p, err := terminal.ReadPassword(fd)
if err != nil {
tlog.Fatal.Printf("Could not read password from terminal: %v\n", err)
os.Exit(exitcodes.ReadPassword)
return nil, fmt.Errorf("Could not read password from terminal: %v\n", err)
}
fmt.Fprintf(os.Stderr, "\n")
if len(p) == 0 {
tlog.Fatal.Println("Password is empty")
os.Exit(exitcodes.PasswordEmpty)
return nil, fmt.Errorf("Password is empty")
}
return p
return p, nil
}
// readPasswordStdin reads a line from stdin.
// It exits with a fatal error on read error or empty result.
func readPasswordStdin(prompt string) []byte {
func readPasswordStdin(prompt string) ([]byte, error) {
tlog.Info.Printf("Reading %s from stdin", prompt)
p := readLineUnbuffered(os.Stdin)
if len(p) == 0 {
tlog.Fatal.Printf("Got empty %s from stdin", prompt)
os.Exit(exitcodes.ReadPassword)
p, err := readLineUnbuffered(os.Stdin)
if err != nil {
return nil, err
}
return p
if len(p) == 0 {
return nil, fmt.Errorf("Got empty %s from stdin", prompt)
}
return p, nil
}
// readPasswordExtpass executes the "extpass" program and returns the first line
// of the output.
// Exits on read error or empty result.
func readPasswordExtpass(extpass []string) []byte {
func readPasswordExtpass(extpass []string) ([]byte, error) {
var parts []string
if len(extpass) == 1 {
parts = strings.Split(extpass[0], " ")
@ -109,50 +113,47 @@ func readPasswordExtpass(extpass []string) []byte {
cmd.Stderr = os.Stderr
pipe, err := cmd.StdoutPipe()
if err != nil {
tlog.Fatal.Printf("extpass pipe setup failed: %v", err)
os.Exit(exitcodes.ReadPassword)
return nil, fmt.Errorf("extpass pipe setup failed: %v", err)
}
err = cmd.Start()
if err != nil {
tlog.Fatal.Printf("extpass cmd start failed: %v", err)
os.Exit(exitcodes.ReadPassword)
return nil, fmt.Errorf("extpass cmd start failed: %v", err)
}
p, err := readLineUnbuffered(pipe)
if err != nil {
return nil, err
}
p := readLineUnbuffered(pipe)
pipe.Close()
err = cmd.Wait()
if err != nil {
tlog.Fatal.Printf("extpass program returned an error: %v", err)
os.Exit(exitcodes.ReadPassword)
return nil, fmt.Errorf("extpass program returned an error: %v", err)
}
if len(p) == 0 {
tlog.Fatal.Println("extpass: password is empty")
os.Exit(exitcodes.ReadPassword)
return nil, fmt.Errorf("extpass: password is empty")
}
return p
return p, nil
}
// readLineUnbuffered reads single bytes from "r" util it gets "\n" or EOF.
// The returned string does NOT contain the trailing "\n".
func readLineUnbuffered(r io.Reader) (l []byte) {
func readLineUnbuffered(r io.Reader) (l []byte, err error) {
b := make([]byte, 1)
for {
if len(l) > maxPasswordLen {
tlog.Fatal.Printf("fatal: maximum password length of %d bytes exceeded", maxPasswordLen)
os.Exit(exitcodes.ReadPassword)
return nil, fmt.Errorf("fatal: maximum password length of %d bytes exceeded", maxPasswordLen)
}
n, err := r.Read(b)
if err == io.EOF {
return l
return l, nil
}
if err != nil {
tlog.Fatal.Printf("readLineUnbuffered: %v", err)
os.Exit(exitcodes.ReadPassword)
return nil, fmt.Errorf("readLineUnbuffered: %v", err)
}
if n == 0 {
continue
}
if b[0] == '\n' {
return l
return l, nil
}
l = append(l, b...)
}

View File

@ -8,12 +8,20 @@ import (
)
// Provide password via stdin, terminated by "\n".
//
// The TEST_SLAVE magic is explained at
// https://talks.golang.org/2014/testing.slide#23 , mirror:
// http://web.archive.org/web/20200426174352/https://talks.golang.org/2014/testing.slide#23
func TestStdin(t *testing.T) {
p1 := "g55434t55wef"
if os.Getenv("TEST_SLAVE") == "1" {
p2 := string(readPasswordStdin("foo"))
if p1 != p2 {
fmt.Fprintf(os.Stderr, "%q != %q", p1, p2)
p2, err := readPasswordStdin("foo")
if err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
if p1 != string(p2) {
fmt.Fprintf(os.Stderr, "%q != %q", p1, string(p2))
os.Exit(1)
}
return
@ -41,12 +49,20 @@ func TestStdin(t *testing.T) {
// Provide password via stdin, terminated by EOF (pipe close). This should not
// hang.
//
// The TEST_SLAVE magic is explained at
// https://talks.golang.org/2014/testing.slide#23 , mirror:
// http://web.archive.org/web/20200426174352/https://talks.golang.org/2014/testing.slide#23
func TestStdinEof(t *testing.T) {
p1 := "asd45as5f4a36"
if os.Getenv("TEST_SLAVE") == "1" {
p2 := string(readPasswordStdin("foo"))
if p1 != p2 {
fmt.Fprintf(os.Stderr, "%q != %q", p1, p2)
p2, err := readPasswordStdin("foo")
if err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
if p1 != string(p2) {
fmt.Fprintf(os.Stderr, "%q != %q", p1, string(p2))
os.Exit(1)
}
return
@ -76,7 +92,10 @@ func TestStdinEof(t *testing.T) {
// Provide empty password via stdin
func TestStdinEmpty(t *testing.T) {
if os.Getenv("TEST_SLAVE") == "1" {
readPasswordStdin("foo")
_, err := readPasswordStdin("foo")
if err != nil {
os.Exit(1)
}
}
cmd := exec.Command(os.Args[0], "-test.run=TestStdinEmpty$")
cmd.Env = append(os.Environ(), "TEST_SLAVE=1")

View File

@ -13,6 +13,8 @@ const (
// On MacOS ExFAT, all empty files share inode number 1:
// https://github.com/rfjakob/gocryptfs/issues/585
QuirkDuplicateIno1
// QuirkNoUserXattr means that user.* xattrs are not supported
QuirkNoUserXattr
)
func logQuirk(s string) {

View File

@ -27,5 +27,9 @@ func DetectQuirks(cipherdir string) (q uint64) {
q |= QuirkBrokenFalloc
}
if uint32(st.Type) == unix.TMPFS_MAGIC {
logQuirk("tmpfs detected, no extended attributes except acls will work.")
}
return q
}

View File

@ -21,8 +21,11 @@ const (
// O_PATH is only defined on Linux
O_PATH = 0
// RENAME_NOREPLACE is only defined on Linux
RENAME_NOREPLACE = 0
// Only exists on Linux. Define here to fix build failure, even though
// we will never see the flags.
RENAME_NOREPLACE = 1
RENAME_EXCHANGE = 2
RENAME_WHITEOUT = 4
// KAUTH_UID_NONE and KAUTH_GID_NONE are special values to
// revert permissions to the process credentials.

View File

@ -28,8 +28,10 @@ const (
// O_PATH is only defined on Linux
O_PATH = unix.O_PATH
// RENAME_NOREPLACE is only defined on Linux
// Only defined on Linux
RENAME_NOREPLACE = unix.RENAME_NOREPLACE
RENAME_WHITEOUT = unix.RENAME_WHITEOUT
RENAME_EXCHANGE = unix.RENAME_EXCHANGE
)
var preallocWarn sync.Once

14
main.go
View File

@ -55,11 +55,15 @@ func loadConfig(args *argContainer) (masterkey []byte, cf *configfile.ConfFile,
if cf.IsFeatureFlagSet(configfile.FlagFIDO2) {
if args.fido2 == "" {
tlog.Fatal.Printf("Masterkey encrypted using FIDO2 token; need to use the --fido2 option.")
os.Exit(exitcodes.Usage)
return nil, nil, exitcodes.NewErr("", exitcodes.Usage)
}
pw = fido2.Secret(args.fido2, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
} else {
pw = readpassword.Once([]string(args.extpass), []string(args.passfile), "")
pw, err = readpassword.Once([]string(args.extpass), []string(args.passfile), "")
if err != nil {
tlog.Fatal.Println(err)
return nil, nil, exitcodes.NewErr("", exitcodes.ReadPassword)
}
}
tlog.Info.Println("Decrypting master key")
masterkey, err = cf.DecryptMasterKey(pw)
@ -93,7 +97,11 @@ func changePassword(args *argContainer) {
os.Exit(exitcodes.Usage)
}
tlog.Info.Println("Please enter your new password.")
newPw := readpassword.Twice([]string(args.extpass), []string(args.passfile))
newPw, err := readpassword.Twice([]string(args.extpass), []string(args.passfile))
if err != nil {
tlog.Fatal.Println(err)
os.Exit(exitcodes.ReadPassword)
}
logN := confFile.ScryptObject.LogN()
if args._explicitScryptn {
logN = args.scryptn

View File

@ -39,8 +39,12 @@ func unhexMasterKey(masterkey string, fromStdin bool) []byte {
func handleArgsMasterkey(args *argContainer) (masterkey []byte) {
// "-masterkey=stdin"
if args.masterkey == "stdin" {
in := string(readpassword.Once(nil, nil, "Masterkey"))
return unhexMasterKey(in, true)
in, err := readpassword.Once(nil, nil, "Masterkey")
if err != nil {
tlog.Fatal.Println(err)
os.Exit(exitcodes.ReadPassword)
}
return unhexMasterKey(string(in), true)
}
// "-masterkey=941a6029-3adc6a1c-..."
if args.masterkey != "" {

View File

@ -1,14 +1,23 @@
#!/bin/bash
if [[ -z $TMPDIR ]]; then
set -eu
VERBOSE=0
for i in "$@" ; do
if [[ $i == "-v" ]] ; then
VERBOSE=1
set -x
break
fi
done
if [[ -z ${TMPDIR:-} ]]; then
TMPDIR=/var/tmp
export TMPDIR
else
echo "Using TMPDIR=$TMPDIR"
fi
set -eu
cd "$(dirname "$0")"
export GO111MODULE=on
MYNAME=$(basename "$0")
@ -53,7 +62,7 @@ if ! go tool | grep vet > /dev/null ; then
elif [[ -d vendor ]] ; then
echo "vendor directory exists, skipping 'go tool vet'"
else
go vet "$@" ./...
go vet ./...
fi
if command -v shellcheck > /dev/null ; then
@ -63,10 +72,18 @@ else
echo "shellcheck not installed - skipping"
fi
# We don't want all the subprocesses
# holding the lock file open
# vvvvv
go test -count 1 ./... "$@" 200>&-
EXTRA_ARGS=""
if [[ $VERBOSE -eq 1 ]]; then
# Disabling parallelism disables per-package output buffering, hence enabling
# live streaming of result output. And seeing where things hang.
EXTRA_ARGS="-p 1"
fi
# We don't want all the subprocesses
# holding the lock file open
# vvvvv
# shellcheck disable=SC2086
go test -count 1 $EXTRA_ARGS ./... "$@" 200>&-
# ^^^^^^^^
# Disable result caching

View File

@ -398,15 +398,37 @@ func TestShadows(t *testing.T) {
}
// TestMountPasswordIncorrect makes sure the correct exit code is used when the password
// was incorrect while mounting
// was incorrect while mounting.
// Also checks that we don't leave a socket file behind.
func TestMountPasswordIncorrect(t *testing.T) {
cDir := test_helpers.InitFS(t) // Create filesystem with password "test"
ctlSock := cDir + ".sock"
pDir := cDir + ".mnt"
err := test_helpers.Mount(cDir, pDir, false, "-extpass", "echo WRONG", "-wpanic=false")
err := test_helpers.Mount(cDir, pDir, false, "-extpass", "echo WRONG", "-wpanic=false", "-ctlsock", ctlSock)
exitCode := test_helpers.ExtractCmdExitCode(err)
if exitCode != exitcodes.PasswordIncorrect {
t.Errorf("want=%d, got=%d", exitcodes.PasswordIncorrect, exitCode)
}
if _, err := os.Stat(ctlSock); err == nil {
t.Errorf("socket file %q left behind", ctlSock)
}
}
// TestMountPasswordEmpty makes sure the correct exit code is used when the password
// was empty while mounting.
// Also checks that we don't leave a socket file behind (https://github.com/rfjakob/gocryptfs/issues/634).
func TestMountPasswordEmpty(t *testing.T) {
cDir := test_helpers.InitFS(t) // Create filesystem with password "test"
ctlSock := cDir + ".sock"
pDir := cDir + ".mnt"
err := test_helpers.Mount(cDir, pDir, false, "-extpass", "true", "-wpanic=false", "-ctlsock", ctlSock)
exitCode := test_helpers.ExtractCmdExitCode(err)
if exitCode != exitcodes.ReadPassword {
t.Errorf("want=%d, got=%d", exitcodes.ReadPassword, exitCode)
}
if _, err := os.Stat(ctlSock); err == nil {
t.Errorf("socket file %q left behind", ctlSock)
}
}
// TestPasswdPasswordIncorrect makes sure the correct exit code is used when the password

View File

@ -14,6 +14,8 @@ import (
"syscall"
"testing"
"golang.org/x/sys/unix"
"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
)
@ -267,15 +269,15 @@ func TestCpWarnings(t *testing.T) {
}
}
// TestSeekData tests that fs.FileLseeker is implemented
// TestSeekData tests that SEEK_DATA works
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 {
var dataOffset int64 = 1024 * 1024 * 1024 // 1 GiB
if _, err = f.Seek(dataOffset, 0); err != nil {
t.Fatal(err)
}
if _, err = f.Write([]byte("foo")); err != nil {
@ -283,18 +285,16 @@ func TestSeekData(t *testing.T) {
}
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)
off, err := f.Seek(1024*1024, unix.SEEK_DATA)
if err != nil {
t.Fatal(err)
}
if off < oneTiB-1024*1024 {
t.Errorf("off=%d, expected=%d\n", off, oneTiB)
if off < dataOffset-1024*1024 {
t.Errorf("off=%d, expected=%d\n", off, dataOffset)
}
f.Close()
}
@ -427,10 +427,11 @@ func TestFsync(t *testing.T) {
}
// force_owner was broken by the v2.0 rewrite:
// The owner was only forced for GETATTR, but not for CREATE or LOOKUP.
// The owner was only forced for GETATTR, but not for CREATE, LOOKUP, MKNOD.
//
// https://github.com/rfjakob/gocryptfs/issues/609
// https://github.com/rfjakob/gocryptfs/pull/610
// https://github.com/rfjakob/gocryptfs/issues/629
func TestForceOwner(t *testing.T) {
cDir := test_helpers.InitFS(t)
os.Chmod(cDir, 0777) // Mount needs to be accessible for us
@ -479,6 +480,18 @@ func TestForceOwner(t *testing.T) {
t.Errorf("GETATTR returned uid or gid != 1234: %#v", st)
}
// Test MKNOD
sock := pDir + "/sock"
if err := syscall.Mknod(sock, syscall.S_IFSOCK|0600, 0); err != nil {
t.Fatal(err)
}
if err := syscall.Stat(sock, &st); err != nil {
t.Fatal(err)
}
if st.Uid != 1234 || st.Gid != 1234 {
t.Errorf("MKNOD 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")

View File

@ -0,0 +1,129 @@
// +build linux
package defaults
import (
"io/ioutil"
"os"
"strings"
"testing"
"golang.org/x/sys/unix"
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
)
// https://github.com/rfjakob/gocryptfs/issues/641
//
// I was trying to run the Docker daemon with the recommended overlay2 storage driver, and encrypt its `/var/lib/docker` directory using gocryptfs. overlay2 was giving me the following errors:
// ```
// Jan 21 19:09:43 friedhelm.rankenste.in kernel: overlayfs: upper fs does not support tmpfile.
// Jan 21 19:09:43 friedhelm.rankenste.in kernel: overlayfs: upper fs does not support RENAME_WHITEOUT.
// Jan 21 19:09:43 friedhelm.rankenste.in kernel: overlayfs: upper fs missing required features.
// ```
func TestRenameWhiteout(t *testing.T) {
short := t.Name() + ".short"
long := t.Name() + strings.Repeat(".long", 200/len(".long"))
names := [][]string{
// short to short
{short + "s2s", short + "s2s2"},
// short to long
{short + "s2l", long + "s2l2"},
// long to short
{long + "l2s", short + "l2s2"},
// long to long
{long + "l2l", short + "l2l2"},
}
for _, flags := range []uint{syscallcompat.RENAME_WHITEOUT, syscallcompat.RENAME_WHITEOUT | syscallcompat.RENAME_NOREPLACE} {
for _, n := range names {
pSrc := test_helpers.DefaultPlainDir + "/" + n[0]
pDst := test_helpers.DefaultPlainDir + "/" + n[1]
if err := ioutil.WriteFile(pSrc, nil, 0200); err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
err := unix.Renameat2(-1, pSrc, -1, pDst, flags)
if err != nil {
t.Error(err)
}
// readdir should not choke on leftover or missing .name files
dir, err := os.Open(test_helpers.DefaultPlainDir)
if err != nil {
t.Fatal(err)
}
defer dir.Close()
_, err = dir.Readdir(0)
if err != nil {
t.Error(err)
}
// pSrc should now be a character device 0 file
var st unix.Stat_t
err = unix.Stat(pSrc, &st)
if err != nil {
t.Error(err)
}
if !(st.Mode&unix.S_IFMT == unix.S_IFCHR) {
t.Error("not a device file")
}
if st.Rdev != 0 {
t.Errorf("want device 0, have %d", st.Rdev)
}
unix.Unlink(pSrc)
unix.Unlink(pDst)
}
}
}
func TestRenameExchange(t *testing.T) {
short := t.Name() + ".short"
long := t.Name() + strings.Repeat(".long", 200/len(".long"))
names := [][]string{
// short to short
{short + "s2s", short + "s2s2"},
// short to long
{short + "s2l", long + "s2l2"},
// long to short
{long + "l2s", short + "l2s2"},
// long to long
{long + "l2l", short + "l2l2"},
}
for _, n := range names {
pSrc := test_helpers.DefaultPlainDir + "/" + n[0]
pDst := test_helpers.DefaultPlainDir + "/" + n[1]
if err := ioutil.WriteFile(pSrc, nil, 0200); err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
if err := ioutil.WriteFile(pDst, nil, 0200); err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
err := unix.Renameat2(-1, pSrc, -1, pDst, unix.RENAME_EXCHANGE)
if err != nil {
t.Error(err)
}
// readdir should not choke on leftover or missing .name files
dir, err := os.Open(test_helpers.DefaultPlainDir)
if err != nil {
t.Fatal(err)
}
defer dir.Close()
_, err = dir.Readdir(0)
if err != nil {
t.Error(err)
}
}
}
// Looks like the FUSE protocol does support O_TMPFILE yet
func TestOTmpfile(t *testing.T) {
p := test_helpers.DefaultPlainDir + "/" + t.Name()
fd, err := unix.Openat(-1, p, unix.O_TMPFILE, 0600)
if err != nil {
t.Skip(err)
}
unix.Close(fd)
}

View File

@ -259,7 +259,7 @@ func TestStatfs(t *testing.T) {
}
}
// TestSeekData tests that fs.FileLseeker is implemented
// TestSeekData tests that SEEK_DATA works
func TestSeekData(t *testing.T) {
if !plaintextnames {
t.Skip()
@ -270,8 +270,8 @@ func TestSeekData(t *testing.T) {
if err != nil {
t.Fatal(err)
}
var oneTiB int64 = 1024 * 1024 * 1024 * 1024
if _, err = f.Seek(oneTiB, 0); err != nil {
var dataOffset int64 = 1 * 1024 * 1024 * 1024 // 1 GiB
if _, err = f.Seek(dataOffset, 0); err != nil {
t.Fatal(err)
}
if _, err = f.Write([]byte("foo")); err != nil {
@ -279,19 +279,17 @@ func TestSeekData(t *testing.T) {
}
f.Close()
const SEEK_DATA = 3
fn2 := filepath.Join(dirB, t.Name())
f, err = os.Open(fn2)
if err != nil {
t.Fatal(err)
}
off, err := f.Seek(1024*1024, SEEK_DATA)
off, err := f.Seek(1024*1024, unix.SEEK_DATA)
if err != nil {
t.Fatal(err)
}
if off < oneTiB-1024*1024 {
t.Errorf("off=%d, expected=%d\n", off, oneTiB)
if off < dataOffset-1024*1024 {
t.Errorf("off=%d, expected=%d\n", off, dataOffset)
}
f.Close()
}

View File

@ -179,7 +179,13 @@ func TestDiskFull(t *testing.T) {
t.Fatal(err)
}
defer syscall.Unlink(ext4img)
defer syscall.Unmount(ext4mnt, 0)
defer func() {
const MNT_DETACH = 2
err := syscall.Unmount(ext4mnt, MNT_DETACH)
if err != nil {
t.Log(err)
}
}()
// gocryptfs -init
cipherdir := ext4mnt + "/a"
@ -362,3 +368,34 @@ func TestBtrfsQuirks(t *testing.T) {
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)
}

View File

@ -37,7 +37,10 @@ type mountInfo struct {
func Mount(c string, p string, showOutput bool, extraArgs ...string) error {
args := []string{"-q", "-wpanic", "-nosyslog", "-fg", fmt.Sprintf("-notifypid=%d", os.Getpid())}
args = append(args, extraArgs...)
//args = append(args, "-fusedebug")
if _, isset := os.LookupEnv("FUSEDEBUG"); isset {
fmt.Println("FUSEDEBUG is set, enabling -fusedebug")
args = append(args, "-fusedebug")
}
//args = append(args, "-d")
args = append(args, c, p)

View File

@ -369,3 +369,17 @@ func TestAcl(t *testing.T) {
t.Error(err)
}
}
// TestSlashInName checks that slashes in xattr names are allowed
// https://github.com/rfjakob/gocryptfs/issues/627
func TestSlashInName(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/" + t.Name()
err := ioutil.WriteFile(fn, []byte("12345"), 0700)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
err = setGetRmList3(fn, "user.foo@https://bar", []byte("val"))
if err != nil {
t.Error(err)
}
}