diff --git a/internal/fusefrontend/openbackingdir.go b/internal/fusefrontend/openbackingdir.go index 1cca81b..e9e10db 100644 --- a/internal/fusefrontend/openbackingdir.go +++ b/internal/fusefrontend/openbackingdir.go @@ -38,7 +38,11 @@ func (fs *FS) openBackingDir(relPath string) (dirfd int, cName string, err error return dirfd, ".", nil } name := filepath.Base(relPath) - cName = fs.nameTransform.EncryptAndHashName(name, iv) + cName, err = fs.nameTransform.EncryptAndHashName(name, iv) + if err != nil { + syscall.Close(dirfd) + return -1, "", err + } return dirfd, cName, nil } // Open cipherdir (following symlinks) @@ -58,7 +62,11 @@ func (fs *FS) openBackingDir(relPath string) (dirfd int, cName string, err error syscall.Close(dirfd) return -1, "", err } - cName = fs.nameTransform.EncryptAndHashName(name, iv) + cName, err = fs.nameTransform.EncryptAndHashName(name, iv) + if err != nil { + syscall.Close(dirfd) + return -1, "", err + } // Last part? We are done. if i == len(parts)-1 { fs.dirCache.Store(dirRelPath, dirfd, iv) diff --git a/internal/nametransform/diriv.go b/internal/nametransform/diriv.go index 09e9dd5..da33aee 100644 --- a/internal/nametransform/diriv.go +++ b/internal/nametransform/diriv.go @@ -8,8 +8,6 @@ import ( "path/filepath" "syscall" - "golang.org/x/sys/unix" - "github.com/rfjakob/gocryptfs/internal/cryptocore" "github.com/rfjakob/gocryptfs/internal/syscallcompat" "github.com/rfjakob/gocryptfs/internal/tlog" @@ -97,12 +95,17 @@ func WriteDirIVAt(dirfd int) error { // encryptAndHashName encrypts "name" and hashes it to a longname if it is // too long. -func (be *NameTransform) EncryptAndHashName(name string, iv []byte) string { - cName := be.EncryptName(name, iv) - if be.longNames && len(cName) > unix.NAME_MAX { - return be.HashLongName(cName) +// Returns ENAMETOOLONG if "name" is longer than 255 bytes. +func (be *NameTransform) EncryptAndHashName(name string, iv []byte) (string, error) { + // Prevent the user from creating files longer than 255 chars. + if len(name) > NameMax { + return "", syscall.ENAMETOOLONG } - return cName + cName := be.EncryptName(name, iv) + if be.longNames && len(cName) > NameMax { + return be.HashLongName(cName), nil + } + return cName, nil } // Dir is like filepath.Dir but returns "" instead of ".". diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go index 638a9eb..20fbede 100644 --- a/internal/nametransform/names.go +++ b/internal/nametransform/names.go @@ -12,6 +12,11 @@ import ( "github.com/rfjakob/gocryptfs/internal/tlog" ) +const ( + // Like ext4, we allow at most 255 bytes for a file name. + NameMax = 255 +) + // NameTransform is used to transform filenames. type NameTransform struct { emeCipher *eme.EMECipher diff --git a/tests/matrix/matrix_test.go b/tests/matrix/matrix_test.go index be47990..33d2ab2 100644 --- a/tests/matrix/matrix_test.go +++ b/tests/matrix/matrix_test.go @@ -377,6 +377,57 @@ func TestRename(t *testing.T) { test_helpers.TestRename(t, test_helpers.DefaultPlainDir) } +// Test that names of all lengths work +func TestNameLengths(t *testing.T) { + f, err := os.Open(test_helpers.DefaultPlainDir) + if err != nil { + t.Fatal(err) + } + entries, err := f.Readdirnames(0) + if err != nil { + t.Fatal(err) + } + f.Close() + cnt1 := len(entries) + + wd := test_helpers.DefaultPlainDir + "/" + name := "x" + for len(name) < 2000 { + f, err := os.Create(wd + name + "x") + if err != nil { + break + } + name = name + "x" + f.Close() + f, err = os.Open(test_helpers.DefaultPlainDir) + if err != nil { + t.Fatal(err) + } + // In v1.7-rc2, we had a bug that allowed creation of too-long names. + // This threw errors in like this in READDIR: + // + // OpenDir ".": invalid entry "gocryptfs.longname.wrE-izsR9ciEkP7JSCFDrk_d_Nj4mQo1dGY6hjuixAU=": + // Could not read .name: ReadLongName: size=345 > limit=344 + // + entries, err = f.Readdirnames(0) + if err != nil { + t.Fatal(err) + } + f.Close() + cnt2 := len(entries) + if cnt2 != cnt1+1 { + t.Fatalf("len=%d: expected %d dir entries, have %d: %v", len(name), cnt1+1, cnt2, entries) + } + err = syscall.Unlink(wd + name) + if err != nil { + t.Fatal(err) + } + } + if len(name) != 255 { + t.Errorf("maxlen=%d", len(name)) + } +} + func TestLongNames(t *testing.T) { fi, err := ioutil.ReadDir(test_helpers.DefaultCipherDir) if err != nil {