diff options
-rw-r--r-- | cli_args.go | 5 | ||||
-rw-r--r-- | internal/nametransform/names.go | 22 | ||||
-rw-r--r-- | mount.go | 11 | ||||
-rw-r--r-- | tests/cli/cli_test.go | 41 |
4 files changed, 75 insertions, 4 deletions
diff --git a/cli_args.go b/cli_args.go index 0462fb4..a036a2a 100644 --- a/cli_args.go +++ b/cli_args.go @@ -32,8 +32,8 @@ type argContainer struct { dev, nodev, suid, nosuid, exec, noexec, rw, ro bool masterkey, mountpoint, cipherdir, cpuprofile, memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string - // -extpass can be passed multiple times - extpass multipleStrings + // -extpass and -badname can be passed multiple times + extpass, badname multipleStrings // For reverse mode, several ways to specify exclusions. All can be specified multiple times. exclude, excludeWildcard, excludeFrom multipleStrings // Configuration file name override @@ -199,6 +199,7 @@ func parseCliOpts() (args argContainer) { // -extpass flagSet.Var(&args.extpass, "extpass", "Use external program for the password prompt") + flagSet.Var(&args.badname, "badname", "Glob pattern invalid file names that should be shown") flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+ "successful mount - used internally for daemonization") diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go index d5c2c8b..de70bce 100644 --- a/internal/nametransform/names.go +++ b/internal/nametransform/names.go @@ -5,6 +5,7 @@ import ( "bytes" "crypto/aes" "encoding/base64" + "path/filepath" "syscall" "github.com/rfjakob/eme" @@ -35,6 +36,8 @@ type NameTransform struct { // B64 = either base64.URLEncoding or base64.RawURLEncoding, depending // on the Raw64 feature flag B64 *base64.Encoding + // Patterns to bypass decryption + BadnamePatterns []string } // New returns a new NameTransform instance. @@ -50,9 +53,24 @@ func New(e *eme.EMECipher, longNames bool, raw64 bool) *NameTransform { } } -// DecryptName decrypts a base64-encoded encrypted filename "cipherName" using the -// initialization vector "iv". +// 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) DecryptName(cipherName string, iv []byte) (string, error) { + res, err := n.decryptName(cipherName, iv) + if err != nil { + for _, pattern := range n.BadnamePatterns { + match, err := filepath.Match(pattern, cipherName) + if err == nil && match { // Pattern should have been validated already + return "GOCRYPTFS_BAD_NAME " + cipherName, nil + } + } + } + return res, err +} + +// decryptName decrypts a base64-encoded encrypted filename "cipherName" using the +// initialization vector "iv". +func (n *NameTransform) decryptName(cipherName string, iv []byte) (string, error) { bin, err := n.B64.DecodeString(cipherName) if err != nil { return "", err @@ -286,6 +286,17 @@ func initFuseFrontend(args *argContainer) (pfs pathfs.FileSystem, wipeKeys func( cCore := cryptocore.New(masterkey, cryptoBackend, contentenc.DefaultIVBits, args.hkdf, args.forcedecode) cEnc := contentenc.New(cCore, contentenc.DefaultBS, args.forcedecode) nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames, args.raw64) + // Init badname patterns + nameTransform.BadnamePatterns = make([]string, 0) + for _, pattern := range args.badname { + _, err := filepath.Match(pattern, "") // Make sure pattern is valid + if err != nil { + tlog.Fatal.Printf("-badname: invalid pattern %q supplied", pattern) + os.Exit(exitcodes.Usage) + } else { + nameTransform.BadnamePatterns = append(nameTransform.BadnamePatterns, pattern) + } + } // After the crypto backend is initialized, // we can purge the master key from memory. for i := range masterkey { diff --git a/tests/cli/cli_test.go b/tests/cli/cli_test.go index 3e1bf42..232c8cc 100644 --- a/tests/cli/cli_test.go +++ b/tests/cli/cli_test.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "strconv" + "strings" "syscall" "testing" "time" @@ -644,3 +645,43 @@ func TestSymlinkedCipherdir(t *testing.T) { t.Errorf("wrong Readdirnames result: %v", names) } } + +func TestBypass(t *testing.T) { + dir := test_helpers.InitFS(t) + mnt := dir + ".mnt" + + test_helpers.MountOrFatal(t, dir, mnt, "-badname=*", "-extpass=echo test") + defer test_helpers.UnmountPanic(mnt) + + file := mnt + "/file" + err := ioutil.WriteFile(file, []byte("somecontent"), 0600) + if err != nil { + t.Fatal(err) + } + + invalid_file_name := "invalid_file" + invalid_file := dir + "/" + invalid_file_name + err = ioutil.WriteFile(invalid_file, []byte("somecontent"), 0600) + if err != nil { + t.Fatal(err) + } + + f, err := os.Open(mnt) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + names, err := f.Readdirnames(0) + found := false + for _, name := range names { + if strings.Contains(name, invalid_file_name) { + found = true + break + } + } + + if !found { + t.Errorf("did not find invalid name %s in %v", invalid_file_name, names) + } +} |