diff options
author | DerDonut | 2021-06-17 08:11:33 +0200 |
---|---|---|
committer | Jakob Unterwurzacher | 2021-06-20 18:09:21 +0200 |
commit | a611810ff46ed0899d677f24c330a994ad125bfb (patch) | |
tree | 6f0abcf6c505374beef416c664b91e6e885e5263 /internal | |
parent | cdddd1d711c3398aa1093090296139aa3d331859 (diff) |
Badname file content access
This proposal is the counterpart of the modifications from the `-badname`
parameter. It modifies the plain -> cipher mapping for filenames when using
`-badname` parameter. The new function `EncryptAndHashBadName` tries to find a
cipher filename for the given plain name with the following steps:
1. If `badname` is disabled or direct mapping is successful: Map directly
(default and current behaviour)
2. If a file with badname flag has a valid cipher file, this is returned
(=File just ends with the badname flag)
3. If a file with a badname flag exists where only the badname flag was added,
this is returned (=File cipher name could not be decrypted by function
`DecryptName` and just the badname flag was added)
4. Search for all files which cipher file name extists when cropping more and
more characters from the end. If only 1 file is found, return this
5. Return an error otherwise
This allows file access in the file browsers but most important it allows that
you rename files with undecryptable cipher names in the plain directories.
Renaming those files will then generate a proper cipher filename One
backdraft: When mounting the cipher dir with -badname parameter, you can never
create (or rename to) files whose file name ends with the badname file flag
(at the moment this is " GOCRYPTFS_BAD_NAME"). This will cause an error.
I modified the CLI test function to cover additional test cases. Test [Case
7](https://github.com/DerDonut/gocryptfs/blob/badnamecontent/tests/cli/cli_test.go#L712)
cannot be performed since the cli tests are executed in panic mode. The
testing is stopped on error. Since the function`DecryptName` produces internal
errors when hitting non-decryptable file names, this test was omitted.
This implementation is a proposal where I tried to change the minimum amount
of existing code. Another possibility would be instead of creating the new
function `EncryptAndHashBadName` to modify the signature of the existing
function `EncryptAndHashName(name string, iv []byte)` to
`EncryptAndHashName(name string, iv []byte, dirfd int)` and integrate the
functionality into this function directly. You may allow calling with dirfd=-1
or other invalid values an then performing the current functionality.
Diffstat (limited to 'internal')
-rw-r--r-- | internal/fusefrontend/node_helpers.go | 10 | ||||
-rw-r--r-- | internal/fusefrontend/root_node.go | 6 | ||||
-rw-r--r-- | internal/nametransform/diriv.go | 50 | ||||
-rw-r--r-- | internal/nametransform/names.go | 13 |
4 files changed, 75 insertions, 4 deletions
diff --git a/internal/fusefrontend/node_helpers.go b/internal/fusefrontend/node_helpers.go index b2f1d4a..f2d1e5e 100644 --- a/internal/fusefrontend/node_helpers.go +++ b/internal/fusefrontend/node_helpers.go @@ -121,7 +121,15 @@ func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno sy var iv []byte dirfd, iv = rn.dirCache.Lookup(n) if dirfd > 0 { - cName, err := rn.nameTransform.EncryptAndHashName(child, iv) + var cName string + var err error + if rn.nameTransform.HaveBadnamePatterns() { + //BadName allowed, try to determine filenames + cName, err = rn.nameTransform.EncryptAndHashBadName(child, iv, dirfd) + } else { + cName, err = rn.nameTransform.EncryptAndHashName(child, iv) + } + if err != nil { return -1, "", fs.ToErrno(err) } diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go index a830cc4..35b7be0 100644 --- a/internal/fusefrontend/root_node.go +++ b/internal/fusefrontend/root_node.go @@ -245,7 +245,11 @@ func (rn *RootNode) openBackingDir(relPath string) (dirfd int, cName string, err syscall.Close(dirfd) return -1, "", err } - cName, err = rn.nameTransform.EncryptAndHashName(name, iv) + if rn.nameTransform.HaveBadnamePatterns() { + cName, err = rn.nameTransform.EncryptAndHashBadName(name, iv, dirfd) + } else { + cName, err = rn.nameTransform.EncryptAndHashName(name, iv) + } if err != nil { syscall.Close(dirfd) return -1, "", err diff --git a/internal/nametransform/diriv.go b/internal/nametransform/diriv.go index 1d27aa5..d62b3fb 100644 --- a/internal/nametransform/diriv.go +++ b/internal/nametransform/diriv.go @@ -6,11 +6,13 @@ import ( "io" "os" "path/filepath" + "strings" "syscall" "github.com/rfjakob/gocryptfs/internal/cryptocore" "github.com/rfjakob/gocryptfs/internal/syscallcompat" "github.com/rfjakob/gocryptfs/internal/tlog" + "golang.org/x/sys/unix" ) const ( @@ -112,6 +114,54 @@ func (be *NameTransform) EncryptAndHashName(name string, iv []byte) (string, err return cName, nil } +// EncryptAndHashBadName tries to find the "name" substring, which (encrypted and hashed) +// leads to an unique existing file +// Returns ENOENT if cipher file does not exist or is not unique +func (be *NameTransform) EncryptAndHashBadName(name string, iv []byte, dirfd int) (cName string, err error) { + var st unix.Stat_t + var filesFound int + lastFoundName, err := be.EncryptAndHashName(name, iv) + if !strings.HasSuffix(name, BadNameFlag) || err != nil { + //Default mode: same behaviour on error or no BadNameFlag on "name" + return lastFoundName, err + } + //Default mode: Check if File extists without modifications + err = syscallcompat.Fstatat(dirfd, lastFoundName, &st, unix.AT_SYMLINK_NOFOLLOW) + if err == nil { + //file found, return result + return lastFoundName, nil + } + //BadName Mode: check if the name was tranformed without change (badname suffix and undecryptable cipher name) + err = syscallcompat.Fstatat(dirfd, name[:len(name)-len(BadNameFlag)], &st, unix.AT_SYMLINK_NOFOLLOW) + if err == nil { + filesFound++ + lastFoundName = name[:len(name)-len(BadNameFlag)] + } + // search for the longest badname pattern match + for charpos := len(name) - len(BadNameFlag); charpos > 0; charpos-- { + //only use original cipher name and append assumed suffix (without badname flag) + cNamePart, err := be.EncryptName(name[:charpos], iv) + if err != nil { + //expand suffix on error + continue + } + if be.longNames && len(cName) > NameMax { + cNamePart = be.HashLongName(cName) + } + cNameBadReverse := cNamePart + name[charpos:len(name)-len(BadNameFlag)] + err = syscallcompat.Fstatat(dirfd, cNameBadReverse, &st, unix.AT_SYMLINK_NOFOLLOW) + if err == nil { + filesFound++ + lastFoundName = cNameBadReverse + } + } + if filesFound == 1 { + return lastFoundName, nil + } + // more than 1 possible file found, ignore + return "", syscall.ENOENT +} + // Dir is like filepath.Dir but returns "" instead of ".". func Dir(path string) string { d := filepath.Dir(path) diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go index ca28230..f730184 100644 --- a/internal/nametransform/names.go +++ b/internal/nametransform/names.go @@ -15,6 +15,8 @@ import ( const ( // Like ext4, we allow at most 255 bytes for a file name. NameMax = 255 + //BadNameFlag is appended to filenames in plain mode if a ciphername is inavlid but is shown + BadNameFlag = " GOCRYPTFS_BAD_NAME" ) // NameTransformer is an interface used to transform filenames. @@ -22,11 +24,13 @@ type NameTransformer interface { DecryptName(cipherName string, iv []byte) (string, error) EncryptName(plainName string, iv []byte) (string, error) EncryptAndHashName(name string, iv []byte) (string, error) + EncryptAndHashBadName(name string, iv []byte, dirfd int) (string, error) // HashLongName - take the hash of a long string "name" and return // "gocryptfs.longname.[sha256]" // // This function does not do any I/O. HashLongName(name string) string + HaveBadnamePatterns() bool WriteLongNameAt(dirfd int, hashName string, plainName string) error B64EncodeToString(src []byte) string B64DecodeString(s string) ([]byte, error) @@ -70,10 +74,10 @@ func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error for charpos := len(cipherName) - 1; charpos >= nameMin; charpos-- { res, err = n.decryptName(cipherName[:charpos], iv) if err == nil { - return res + cipherName[charpos:] + " GOCRYPTFS_BAD_NAME", nil + return res + cipherName[charpos:] + BadNameFlag, nil } } - return cipherName + " GOCRYPTFS_BAD_NAME", nil + return cipherName + BadNameFlag, nil } } } @@ -135,3 +139,8 @@ func (n *NameTransform) B64EncodeToString(src []byte) string { func (n *NameTransform) B64DecodeString(s string) ([]byte, error) { return n.B64.DecodeString(s) } + +// HaveBadnamePatterns returns true if BadName patterns were provided +func (n *NameTransform) HaveBadnamePatterns() bool { + return len(n.BadnamePatterns) > 0 +} |