libgocryptfs/tests/xattr/xattr_integration_test.go
Sebastian Lackner 5055f39bd5 fusefrontend: Allow to set/remove xattr on directory without read permission.
Setting/removing extended attributes on directories was partially fixed with
commit eff35e60b6. However, on most file systems
it is also possible to do these operations without read access (see tests).

Since we cannot open a write-access fd to a directory, we have to use the
/proc/self/fd trick (already used for ListXAttr) for the other operations aswell.
For simplicity, let's separate the Linux and Darwin code again (basically revert
commit f320b76fd1), and always use the
/proc/self/fd trick on Linux. On Darwin we use the best-effort approach with
openBackingFile() as a fallback.

More discussion about the available options is available in
https://github.com/rfjakob/gocryptfs/issues/308.
2019-01-05 12:34:40 +01:00

347 lines
9.2 KiB
Go

package xattr_tests
// xattr integration tests.
//
// These tests are not integrated into the "matrix" tests because of the need
// to switch TMPDIR to /var/tmp.
// TODO: check if it actually causes trouble in the "matrix" tests.
import (
"bytes"
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"strings"
"syscall"
"testing"
"github.com/pkg/xattr"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/tests/test_helpers"
)
func TestMain(m *testing.M) {
// On modern Linux distributions, /tmp may be on tmpfs,
// which does not support user xattrs. Try /var/tmp instead
if !xattrSupported(test_helpers.TmpDir) && os.TempDir() == "/tmp" {
fmt.Printf("Switching from /tmp to /var/tmp for xattr tests\n")
test_helpers.SwitchTMPDIR("/var/tmp")
}
if !xattrSupported(test_helpers.TmpDir) {
fmt.Printf("xattrs not supported on %q\n", test_helpers.TmpDir)
os.Exit(1)
}
test_helpers.ResetTmpDir(true)
// Write deterministic diriv so encrypted filenames are deterministic.
os.Remove(test_helpers.DefaultCipherDir + "/gocryptfs.diriv")
diriv := []byte("1234567890123456")
err := ioutil.WriteFile(test_helpers.DefaultCipherDir+"/gocryptfs.diriv", diriv, 0400)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, "-zerokey")
r := m.Run()
test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
os.RemoveAll(test_helpers.TmpDir)
os.Exit(r)
}
func setGetRmList(fn string) error {
// List
list, err := xattr.LList(fn)
if err != nil {
return err
}
if len(list) > 0 {
return fmt.Errorf("Should have gotten empty result, got %v", list)
}
attr := "user.foo"
// Set
val1 := []byte("123456789")
err = xattr.LSet(fn, attr, val1)
if err != nil {
return err
}
// Read back
val2, err := xattr.LGet(fn, attr)
if err != nil {
return err
}
if !bytes.Equal(val1, val2) {
return fmt.Errorf("wrong readback value: %v != %v", val1, val2)
}
// Remove
err = xattr.LRemove(fn, attr)
if err != nil {
return err
}
// Read back
val3, err := xattr.LGet(fn, attr)
if err == nil {
return fmt.Errorf("attr is still there after deletion!? val3=%v", val3)
}
// List
list, err = xattr.LList(fn)
if err != nil {
return err
}
if len(list) > 0 {
return fmt.Errorf("Should have gotten empty result, got %v", list)
}
return nil
}
// Test xattr set, get, rm on a regular file.
func TestSetGetRmRegularFile(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestSetGetRmRegularFile"
err := ioutil.WriteFile(fn, []byte("12345"), 0700)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
err = setGetRmList(fn)
if err != nil {
t.Error(err)
}
fi, _ := os.Lstat(fn)
if fi.Size() != 5 {
t.Errorf("file size has changed!? size=%d", fi.Size())
}
}
// Test xattr set, get, rm on a fifo. This should not hang.
func TestSetGetRmFifo(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestSetGetRmFifo"
err := syscall.Mkfifo(fn, 0700)
if err != nil {
t.Fatalf("creating fifo failed: %v", err)
}
// We expect to get EPERM, but we should not hang:
// $ setfattr -n user.foo -v XXXXX fifo
// setfattr: fifo: Operation not permitted
setGetRmList(fn)
}
// Test xattr set, get, rm on a directory. This should not fail with EISDIR.
func TestSetGetRmDir(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestSetGetRmDir"
err := syscall.Mkdir(fn, 0700)
if err != nil {
t.Fatalf("creating directory failed: %v", err)
}
setGetRmList(fn)
}
func TestXattrSetEmpty(t *testing.T) {
attr := "user.foo"
fn := test_helpers.DefaultPlainDir + "/TestXattrSetEmpty1"
err := ioutil.WriteFile(fn, nil, 0700)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
// Make sure it does not exist already
_, err = xattr.LGet(fn, attr)
if err == nil {
t.Fatal("we should have got an error here")
}
// Set empty value
err = xattr.LSet(fn, attr, nil)
if err != nil {
t.Fatal(err)
}
// Read back
val, err := xattr.LGet(fn, attr)
if err != nil {
t.Fatal(err)
}
if len(val) != 0 {
t.Errorf("wrong length: want=0 have=%d", len(val))
}
// Overwrite empty value with something
val1 := []byte("xyz123")
err = xattr.LSet(fn, attr, val1)
if err != nil {
t.Fatal(err)
}
// Read back
val2, err := xattr.LGet(fn, attr)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(val1, val2) {
t.Fatalf("wrong readback value: %v != %v", val1, val2)
}
// Overwrite something with empty value
err = xattr.LSet(fn, attr, nil)
if err != nil {
t.Fatal(err)
}
// Read back
val, err = xattr.LGet(fn, attr)
if err != nil {
t.Fatal(err)
}
if len(val) != 0 {
t.Errorf("wrong length: want=0 have=%d", len(val2))
}
}
func TestXattrList(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestXattrList"
err := ioutil.WriteFile(fn, nil, 0700)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
val := []byte("xxxxxxxxyyyyyyyyyyyyyyyzzzzzzzzzzzzz")
num := 20
for i := 1; i <= num; i++ {
attr := fmt.Sprintf("user.TestXattrList.%02d", i)
err = xattr.LSet(fn, attr, val)
if err != nil {
t.Fatal(err)
}
}
names, err := xattr.LList(fn)
if err != nil {
t.Fatal(err)
}
if len(names) != num {
t.Errorf("wrong number of names, want=%d have=%d", num, len(names))
}
for _, n := range names {
if !strings.HasPrefix(n, "user.TestXattrList.") {
t.Errorf("unexpected attr name: %q", n)
}
}
}
func xattrSupported(path string) bool {
_, err := xattr.LGet(path, "user.xattrSupported-dummy-value")
if err == nil {
return true
}
err2 := err.(*xattr.Error)
if err2.Err == syscall.EOPNOTSUPP {
return false
}
return true
}
func TestBase64XattrRead(t *testing.T) {
attrName := "user.test"
attrName2 := "user.test2"
encryptedAttrName := "user.gocryptfs.LB1kHHVrX1OEBdLmj3LTKw"
encryptedAttrName2 := "user.gocryptfs.d2yn5l7-0zUVqviADw-Oyw"
attrValue := fmt.Sprintf("test.%d", cryptocore.RandUint64())
fileName := "TestBase64Xattr"
encryptedFileName := "BaGak7jIoqAZQMlP0N5uCw"
plainFn := test_helpers.DefaultPlainDir + "/" + fileName
encryptedFn := test_helpers.DefaultCipherDir + "/" + encryptedFileName
err := ioutil.WriteFile(plainFn, nil, 0700)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
if _, err2 := os.Stat(encryptedFn); os.IsNotExist(err2) {
t.Fatalf("encrypted file does not exist: %v", err2)
}
xattr.LSet(plainFn, attrName, []byte(attrValue))
encryptedAttrValue, err1 := xattr.LGet(encryptedFn, encryptedAttrName)
if err1 != nil {
t.Fatal(err1)
}
xattr.LSet(encryptedFn, encryptedAttrName2, encryptedAttrValue)
plainValue, err := xattr.LGet(plainFn, attrName2)
if err != nil || string(plainValue) != attrValue {
t.Fatalf("Attribute binary value decryption error: have=%q want=%q err=%v", string(plainValue), attrValue, err)
}
encryptedAttrValue64 := base64.RawURLEncoding.EncodeToString(encryptedAttrValue)
xattr.LSet(encryptedFn, encryptedAttrName2, []byte(encryptedAttrValue64))
plainValue, err = xattr.LGet(plainFn, attrName2)
if err != nil || string(plainValue) != attrValue {
t.Fatalf("Attribute base64-encoded value decryption error %s != %s %v", string(plainValue), attrValue, err)
}
// Remount with -wpanic=false so gocryptfs does not panics when it sees
// the broken xattrs
test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, "-zerokey", "-wpanic=false")
brokenVals := []string{
"111",
"raw-test-long-block123",
"raw-test-long-block123-xyz11111111111111111111111111111111111111",
"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
}
for _, val := range brokenVals {
xattr.LSet(encryptedFn, encryptedAttrName2, []byte(val))
plainValue, err = xattr.LGet(plainFn, attrName2)
err2, _ := err.(*xattr.Error)
if err == nil || err2.Err != syscall.EIO {
t.Fatalf("Incorrect handling of broken data %s %v", string(plainValue), err)
}
}
}
// Listing xattrs should work even when we don't have read access
func TestList0000File(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestList0000File"
err := ioutil.WriteFile(fn, nil, 0000)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
_, err = xattr.LList(fn)
if err != nil {
t.Error(err)
}
}
// Setting xattrs should work even when we don't have read access
func TestSet0200File(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestSet0200File"
err := ioutil.WriteFile(fn, nil, 0200)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
err = xattr.LSet(fn, "user.foo", []byte("bar"))
if err != nil {
t.Error(err)
}
}
// Listing xattrs should work even when we don't have read access
func TestList0000Dir(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestList0000Dir"
err := syscall.Mkdir(fn, 0000)
if err != nil {
t.Fatalf("creating directory failed: %v", err)
}
_, err = xattr.LList(fn)
os.Chmod(fn, 0700)
if err != nil {
t.Error(err)
}
}
// Setting xattrs should work even when we don't have read access
func TestSet0200Dir(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestSet0200Dir"
err := syscall.Mkdir(fn, 0200)
if err != nil {
t.Fatalf("creating directory failed: %v", err)
}
err = xattr.LSet(fn, "user.foo", []byte("bar"))
os.Chmod(fn, 0700)
if err != nil {
t.Error(err)
}
}