diff options
| -rw-r--r-- | internal/fusefrontend/node_xattr.go | 4 | ||||
| -rw-r--r-- | internal/fusefrontend/root_node.go | 4 | ||||
| -rw-r--r-- | internal/nametransform/names.go | 34 | ||||
| -rw-r--r-- | internal/nametransform/names_test.go | 23 | ||||
| -rw-r--r-- | internal/nametransform/xattr.go | 47 | ||||
| -rw-r--r-- | tests/xattr/xattr_integration_test.go | 14 | 
6 files changed, 110 insertions, 16 deletions
| diff --git a/internal/fusefrontend/node_xattr.go b/internal/fusefrontend/node_xattr.go index 09ee5ef..44bc502 100644 --- a/internal/fusefrontend/node_xattr.go +++ b/internal/fusefrontend/node_xattr.go @@ -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." diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go index 7221be6..39cdef7 100644 --- a/internal/fusefrontend/root_node.go +++ b/internal/fusefrontend/root_node.go @@ -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  	} diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go index e07ccfb..ebe0fb6 100644 --- a/internal/nametransform/names.go +++ b/internal/nametransform/names.go @@ -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 diff --git a/internal/nametransform/names_test.go b/internal/nametransform/names_test.go index b4e98d4..3c26c43 100644 --- a/internal/nametransform/names_test.go +++ b/internal/nametransform/names_test.go @@ -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) +		} +	} +} diff --git a/internal/nametransform/xattr.go b/internal/nametransform/xattr.go new file mode 100644 index 0000000..0aa0fd8 --- /dev/null +++ b/internal/nametransform/xattr.go @@ -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 +} + +// DecryptName 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 +} diff --git a/tests/xattr/xattr_integration_test.go b/tests/xattr/xattr_integration_test.go index efe903b..c968a47 100644 --- a/tests/xattr/xattr_integration_test.go +++ b/tests/xattr/xattr_integration_test.go @@ -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) +	} +} | 
