xattr: optimize storage, store as binary instead of bae64
Values a binary-safe, there is no need to base64-encode them. Old, base64-encoded values are supported transparently on reading. Writing xattr values now always writes them binary.
This commit is contained in:
parent
a276321dea
commit
a41ec2028c
|
@ -36,17 +36,16 @@ func (fs *FS) GetXAttr(path string, attr string, context *fuse.Context) ([]byte,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fuse.ToStatus(err)
|
return nil, fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
cData64, err := xattr.Get(cPath, cAttr)
|
encryptedData, err := xattr.Get(cPath, cAttr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, unpackXattrErr(err)
|
return nil, unpackXattrErr(err)
|
||||||
}
|
}
|
||||||
// xattr data is decrypted like a symlink target
|
data, err := fs.decryptXattrValue(encryptedData)
|
||||||
data, err := fs.decryptSymlinkTarget(string(cData64))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Warn.Printf("GetXAttr: %v", err)
|
tlog.Warn.Printf("GetXAttr: %v", err)
|
||||||
return nil, fuse.EIO
|
return nil, fuse.EIO
|
||||||
}
|
}
|
||||||
return []byte(data), fuse.OK
|
return data, fuse.OK
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetXAttr implements pathfs.Filesystem.
|
// 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)
|
return fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
cAttr := fs.encryptXattrName(attr)
|
cAttr := fs.encryptXattrName(attr)
|
||||||
// xattr data is encrypted like a symlink target
|
cData := fs.encryptXattrValue(data)
|
||||||
cData64 := []byte(fs.encryptSymlinkTarget(string(data)))
|
return unpackXattrErr(xattr.SetWithFlags(cPath, cAttr, cData, flags))
|
||||||
return unpackXattrErr(xattr.SetWithFlags(cPath, cAttr, cData64, flags))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveXAttr implements pathfs.Filesystem.
|
// RemoveXAttr implements pathfs.Filesystem.
|
||||||
|
@ -136,6 +134,37 @@ func (fs *FS) decryptXattrName(cAttr string) (attr string, err error) {
|
||||||
return attr, nil
|
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
|
// unpackXattrErr unpacks an error value that we got from xattr.Get/Set/etc
|
||||||
// and converts it to a fuse status.
|
// and converts it to a fuse status.
|
||||||
func unpackXattrErr(err error) fuse.Status {
|
func unpackXattrErr(err error) fuse.Status {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package defaults
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -11,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/xattr"
|
"github.com/pkg/xattr"
|
||||||
|
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||||
"github.com/rfjakob/gocryptfs/tests/test_helpers"
|
"github.com/rfjakob/gocryptfs/tests/test_helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,6 +29,14 @@ func TestMain(m *testing.M) {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
test_helpers.ResetTmpDir(true)
|
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")
|
test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, "-zerokey")
|
||||||
r := m.Run()
|
r := m.Run()
|
||||||
test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
|
test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
|
||||||
|
@ -161,3 +171,65 @@ func xattrSupported(path string) bool {
|
||||||
}
|
}
|
||||||
return true
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue