aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorJakob Unterwurzacher2021-12-19 14:43:56 +0100
committerJakob Unterwurzacher2021-12-19 14:43:56 +0100
commit64be5de75f42e415198ff5e77de509680b69e0e1 (patch)
tree8897db6d0de0a4c11921bba72de621e30f0ce501 /internal
parenteb42e541828336e9b19e1bc5e087a419835b0c85 (diff)
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
Diffstat (limited to 'internal')
-rw-r--r--internal/fusefrontend/node_xattr.go4
-rw-r--r--internal/fusefrontend/root_node.go4
-rw-r--r--internal/nametransform/names.go34
-rw-r--r--internal/nametransform/names_test.go23
-rw-r--r--internal/nametransform/xattr.go47
5 files changed, 96 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
+}