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
|
// -1 as uint32
|
||||||
const minus1 = ^uint32(0)
|
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
|
// We store encrypted xattrs under this prefix plus the base64-encoded
|
||||||
// encrypted original name.
|
// encrypted original name.
|
||||||
var xattrStorePrefix = "user.gocryptfs."
|
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"
|
// encryptXattrName transforms "user.foo" to "user.gocryptfs.a5sAd4XAa47f5as6dAf"
|
||||||
func (rn *RootNode) encryptXattrName(attr string) (string, error) {
|
func (rn *RootNode) encryptXattrName(attr string) (string, error) {
|
||||||
// xattr names are encrypted like file names, but with a fixed IV.
|
// 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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -282,7 +282,7 @@ func (rn *RootNode) decryptXattrName(cAttr string) (attr string, err error) {
|
|||||||
}
|
}
|
||||||
// Strip "user.gocryptfs." prefix
|
// Strip "user.gocryptfs." prefix
|
||||||
cAttr = cAttr[len(xattrStorePrefix):]
|
cAttr = cAttr[len(xattrStorePrefix):]
|
||||||
attr, err = rn.nameTransform.DecryptName(cAttr, xattrNameIV)
|
attr, err = rn.nameTransform.DecryptXattrName(cAttr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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) {
|
func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) {
|
||||||
res, err := n.decryptName(cipherName, iv)
|
res, err := n.decryptName(cipherName, iv)
|
||||||
if err != nil && n.HaveBadnamePatterns() {
|
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
|
return res, err
|
||||||
}
|
}
|
||||||
@ -79,30 +86,29 @@ func (n *NameTransform) decryptName(cipherName string, iv []byte) (string, error
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if len(bin) == 0 {
|
if len(bin) == 0 {
|
||||||
tlog.Warn.Printf("DecryptName: empty input")
|
tlog.Warn.Printf("decryptName: empty input")
|
||||||
return "", syscall.EBADMSG
|
return "", syscall.EBADMSG
|
||||||
}
|
}
|
||||||
if len(bin)%aes.BlockSize != 0 {
|
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
|
return "", syscall.EBADMSG
|
||||||
}
|
}
|
||||||
bin = n.emeCipher.Decrypt(iv, bin)
|
bin = n.emeCipher.Decrypt(iv, bin)
|
||||||
bin, err = unPad16(bin)
|
bin, err = unPad16(bin)
|
||||||
if err != nil {
|
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
|
return "", syscall.EBADMSG
|
||||||
}
|
}
|
||||||
plain := string(bin)
|
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
|
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).
|
// 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
|
// This function is exported because in some cases, fusefrontend needs access
|
||||||
// to the full (not hashed) name if longname is used.
|
// to the full (not hashed) name if longname is used.
|
||||||
func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 string, err error) {
|
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)
|
tlog.Warn.Printf("EncryptName %q: invalid plainName: %v", plainName, err)
|
||||||
return "", syscall.EBADMSG
|
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 := []byte(plainName)
|
||||||
bin = pad16(bin)
|
bin = pad16(bin)
|
||||||
bin = n.emeCipher.Encrypt(iv, bin)
|
bin = n.emeCipher.Encrypt(iv, bin)
|
||||||
cipherName64 = n.B64.EncodeToString(bin)
|
cipherName64 = n.B64.EncodeToString(bin)
|
||||||
return cipherName64, nil
|
return cipherName64
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptAndHashName encrypts "name" and hashes it to a longname if it is
|
// 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)
|
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