diff options
| -rw-r--r-- | internal/fusefrontend/xattr.go | 43 | ||||
| -rw-r--r-- | tests/xattr/xattr_integration_test.go | 72 | 
2 files changed, 108 insertions, 7 deletions
| diff --git a/internal/fusefrontend/xattr.go b/internal/fusefrontend/xattr.go index 9833368..ac4b15f 100644 --- a/internal/fusefrontend/xattr.go +++ b/internal/fusefrontend/xattr.go @@ -36,17 +36,16 @@ func (fs *FS) GetXAttr(path string, attr string, context *fuse.Context) ([]byte,  	if err != nil {  		return nil, fuse.ToStatus(err)  	} -	cData64, err := xattr.Get(cPath, cAttr) +	encryptedData, err := xattr.Get(cPath, cAttr)  	if err != nil {  		return nil, unpackXattrErr(err)  	} -	// xattr data is decrypted like a symlink target -	data, err := fs.decryptSymlinkTarget(string(cData64)) +	data, err := fs.decryptXattrValue(encryptedData)  	if err != nil {  		tlog.Warn.Printf("GetXAttr: %v", err)  		return nil, fuse.EIO  	} -	return []byte(data), fuse.OK +	return data, fuse.OK  }  // SetXAttr implements pathfs.Filesystem. @@ -65,9 +64,8 @@ func (fs *FS) SetXAttr(path string, attr string, data []byte, flags int, context  		return fuse.ToStatus(err)  	}  	cAttr := fs.encryptXattrName(attr) -	// xattr data is encrypted like a symlink target -	cData64 := []byte(fs.encryptSymlinkTarget(string(data))) -	return unpackXattrErr(xattr.SetWithFlags(cPath, cAttr, cData64, flags)) +	cData := fs.encryptXattrValue(data) +	return unpackXattrErr(xattr.SetWithFlags(cPath, cAttr, cData, flags))  }  // RemoveXAttr implements pathfs.Filesystem. @@ -136,6 +134,37 @@ func (fs *FS) decryptXattrName(cAttr string) (attr string, err error) {  	return attr, nil  } +// encryptXattrValue encrypts the xattr value "data". +// The data is encrypted like a file content block, but without binding it to +// a file location (block number and file id are set to zero). +// Special case: an empty value is encrypted to an empty value. +func (fs *FS) encryptXattrValue(data []byte) (cData []byte) { +	if len(data) == 0 { +		return []byte{} +	} +	return fs.contentEnc.EncryptBlock(data, 0, nil) +} + +// decryptXattrValue decrypts the xattr value "cData". +func (fs *FS) decryptXattrValue(cData []byte) (data []byte, err error) { +	if len(cData) == 0 { +		return []byte{}, nil +	} +	data, err1 := fs.contentEnc.DecryptBlock([]byte(cData), 0, nil) +	if err1 == nil { +		return data, nil +	} +	// This backward compatibility is needed to support old +	// file systems having xattr values base64-encoded. +	cData, err2 := fs.nameTransform.B64.DecodeString(string(cData)) +	if err2 != nil { +		// Looks like the value was not base64-encoded, but just corrupt. +		// Return the original decryption error: err1 +		return nil, err1 +	} +	return fs.contentEnc.DecryptBlock([]byte(cData), 0, nil) +} +  // unpackXattrErr unpacks an error value that we got from xattr.Get/Set/etc  // and converts it to a fuse status.  func unpackXattrErr(err error) fuse.Status { diff --git a/tests/xattr/xattr_integration_test.go b/tests/xattr/xattr_integration_test.go index 50e7fb7..7d4c831 100644 --- a/tests/xattr/xattr_integration_test.go +++ b/tests/xattr/xattr_integration_test.go @@ -2,6 +2,7 @@ package defaults  import (  	"bytes" +	"encoding/base64"  	"fmt"  	"io/ioutil"  	"os" @@ -11,6 +12,7 @@ import (  	"github.com/pkg/xattr" +	"github.com/rfjakob/gocryptfs/internal/cryptocore"  	"github.com/rfjakob/gocryptfs/tests/test_helpers"  ) @@ -27,6 +29,14 @@ func TestMain(m *testing.M) {  		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) @@ -161,3 +171,65 @@ func xattrSupported(path string) bool {  	}  	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.Set(plainFn, attrName, []byte(attrValue)) + +	encryptedAttrValue, err1 := xattr.Get(encryptedFn, encryptedAttrName) +	if err1 != nil { +		t.Fatal(err1) +	} + +	xattr.Set(encryptedFn, encryptedAttrName2, encryptedAttrValue) +	plainValue, err := xattr.Get(plainFn, attrName2) + +	if err != nil || string(plainValue) != attrValue { +		t.Fatalf("Attribute binary value decryption error %s != %s %v", string(plainValue), attrValue, err) +	} + +	encryptedAttrValue64 := base64.RawURLEncoding.EncodeToString(encryptedAttrValue) +	xattr.Set(encryptedFn, encryptedAttrName2, []byte(encryptedAttrValue64)) + +	plainValue, err = xattr.Get(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.Set(encryptedFn, encryptedAttrName2, []byte(val)) +		plainValue, err = xattr.Get(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) +		} +	} +} | 
