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
This commit is contained in:
parent
eb42e54182
commit
64be5de75f
@ -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."
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
47
internal/nametransform/xattr.go
Normal file
47
internal/nametransform/xattr.go
Normal 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user