nametranform, fusefrontend: better errors on invalid names

nametransform.DecryptName() now always returns syscall.EBADMSG if
the name was invalid.

fusefrontend.OpenDir error messages have been normalized.
This commit is contained in:
Jakob Unterwurzacher 2017-05-07 20:58:27 +02:00
parent 68387b470c
commit 26881538e1
2 changed files with 19 additions and 14 deletions

View File

@ -291,8 +291,8 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f
if isLong == nametransform.LongNameContent { if isLong == nametransform.LongNameContent {
cNameLong, err := nametransform.ReadLongName(filepath.Join(cDirAbsPath, cName)) cNameLong, err := nametransform.ReadLongName(filepath.Join(cDirAbsPath, cName))
if err != nil { if err != nil {
tlog.Warn.Printf("Skipping entry %q in dir %q: Could not read .name: %v", tlog.Warn.Printf("OpenDir %q: invalid entry %q: Could not read .name: %v",
cName, cDirName, err) cDirName, cName, err)
errorCount++ errorCount++
continue continue
} }
@ -304,12 +304,13 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f
name, err := fs.nameTransform.DecryptName(cName, cachedIV) name, err := fs.nameTransform.DecryptName(cName, cachedIV)
if err != nil { if err != nil {
tlog.Warn.Printf("Skipping entry %q in dir %q: %s", tlog.Warn.Printf("OpenDir %q: invalid entry %q: %v",
cName, cDirName, err) cDirName, cName, err)
errorCount++ errorCount++
continue continue
} }
// Override the ciphertext name with the plaintext name but reuse the rest
// of the structure
cipherEntries[i].Name = name cipherEntries[i].Name = name
plain = append(plain, cipherEntries[i]) plain = append(plain, cipherEntries[i])
} }
@ -317,8 +318,8 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f
if errorCount > 0 && len(plain) == 0 { if errorCount > 0 && len(plain) == 0 {
// Don't let the user stare on an empty directory. Report that things went // Don't let the user stare on an empty directory. Report that things went
// wrong. // wrong.
tlog.Warn.Printf("All %d entries in directory %q were invalid, returning EIO", tlog.Warn.Printf("OpenDir %q: all %d entries were invalid, returning EIO",
errorCount, cDirName) cDirName, errorCount)
status = fuse.EIO status = fuse.EIO
} }

View File

@ -2,6 +2,7 @@
package nametransform package nametransform
import ( import (
"bytes"
"crypto/aes" "crypto/aes"
"encoding/base64" "encoding/base64"
"syscall" "syscall"
@ -34,10 +35,8 @@ func New(e *eme.EMECipher, longNames bool, raw64 bool) *NameTransform {
} }
} }
// DecryptName - decrypt base64-encoded encrypted filename "cipherName" // DecryptName decrypts a base64-encoded encrypted filename "cipherName" using the
// // initialization vector "iv".
// This function is exported because it allows for a very efficient readdir
// implementation (read IV once, decrypt all names using this function).
func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) { func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) {
bin, err := n.B64.DecodeString(cipherName) bin, err := n.B64.DecodeString(cipherName)
if err != nil { if err != nil {
@ -45,16 +44,21 @@ func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error
} }
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.EINVAL 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.Debug.Printf("pad16 error detail: %v", err) tlog.Debug.Printf("DecryptName: unPad16 error: %v", err)
// unPad16 returns detailed errors including the position of the // unPad16 returns detailed errors including the position of the
// incorrect bytes. Kill the padding oracle by lumping everything into // incorrect bytes. Kill the padding oracle by lumping everything into
// a generic error. // a generic error.
return "", syscall.EINVAL return "", syscall.EBADMSG
}
// A name can never contain a null byte or "/". Make sure we never return those
// to the user, even when we read a corrupted (or fuzzed) filesystem.
if bytes.Contains(bin, []byte{0}) || bytes.Contains(bin, []byte("/")) {
return "", syscall.EBADMSG
} }
plain := string(bin) plain := string(bin)
return plain, err return plain, err