diff options
| -rw-r--r-- | Documentation/MANPAGE.md | 8 | ||||
| -rw-r--r-- | internal/fusefrontend/args.go | 4 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/root_node.go | 20 | ||||
| -rw-r--r-- | mount.go | 1 | ||||
| -rw-r--r-- | tests/reverse/config_custom_test.go | 62 |
5 files changed, 94 insertions, 1 deletions
diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md index 64bbaa8..3ef6500 100644 --- a/Documentation/MANPAGE.md +++ b/Documentation/MANPAGE.md @@ -457,6 +457,14 @@ Use specified config file instead of `CIPHERDIR/gocryptfs.conf`. Applies to: all actions that use a config file: mount, `-fsck`, `-passwd`, `-info`, `-init`. +In `-reverse` mode, this also changes what the encrypted view contains: by +default the config is exposed there as a virtual `gocryptfs.conf` (so a backup +of the encrypted view is self-contained), but with `-config` no `gocryptfs.conf` +is presented. Make sure to back up the config file (or the master key) from its +custom location separately, otherwise the encrypted data cannot be decrypted. +If the custom config file is located inside `CIPHERDIR`, it is hidden from the +encrypted view rather than exposed in encrypted form. + #### -cpuprofile string Write cpu profile to specified file. diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go index ec3d1c2..84e34af 100644 --- a/internal/fusefrontend/args.go +++ b/internal/fusefrontend/args.go @@ -24,6 +24,10 @@ type Args struct { // location. If it is false, reverse mode maps ".gocryptfs.reverse.conf" // to "gocryptfs.conf" in the plaintext dir. ConfigCustom bool + // Config is the path to the config file. In reverse mode, a custom config + // file (-config) located inside Cipherdir is hidden from the encrypted view + // (https://github.com/rfjakob/gocryptfs/issues/1009). + Config string // NoPrealloc disables automatic preallocation before writing NoPrealloc bool // Exclude is a list of paths to make inaccessible, starting match at diff --git a/internal/fusefrontend_reverse/root_node.go b/internal/fusefrontend_reverse/root_node.go index 1a668af..7ac28af 100644 --- a/internal/fusefrontend_reverse/root_node.go +++ b/internal/fusefrontend_reverse/root_node.go @@ -36,6 +36,11 @@ type RootNode struct { contentEnc *contentenc.ContentEnc // Tests whether a path is excluded (hidden) from the user. Used by -exclude. excluder ignore.IgnoreParser + // configPlainPath is the path of a custom config file (-config), relative to + // Cipherdir, set only when that file is located inside Cipherdir. Such a file + // is hidden from the encrypted view, otherwise it would leak in encrypted + // form (https://github.com/rfjakob/gocryptfs/issues/1009). + configPlainPath string // inoMap translates inode numbers from different devices to unique inode // numbers. inoMap *inomap.InoMap @@ -92,6 +97,14 @@ func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransf if len(args.Exclude) > 0 || len(args.ExcludeWildcard) > 0 || len(args.ExcludeFrom) > 0 { rn.excluder = prepareExcluder(args) } + // A custom config file (-config) located inside Cipherdir must be hidden from + // the encrypted view, otherwise it leaks there in encrypted form (#1009). + if args.ConfigCustom && args.Config != "" { + if rel, err := filepath.Rel(args.Cipherdir, args.Config); err == nil && + rel != "." && rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + rn.configPlainPath = rel + } + } return rn } @@ -141,6 +154,11 @@ func (rn *RootNode) isExcludedPlain(pPath string) bool { if pPath == "" || pPath == configfile.ConfReverseName { return false } + // A custom config file (-config) inside Cipherdir is hidden from the + // encrypted view (https://github.com/rfjakob/gocryptfs/issues/1009). + if rn.configPlainPath != "" && pPath == rn.configPlainPath { + return true + } return rn.excluder != nil && rn.excluder.MatchesPath(pPath) } @@ -148,7 +166,7 @@ func (rn *RootNode) isExcludedPlain(pPath string) bool { // pDir is the relative plaintext path to the directory these entries are // from. The entries should be plaintext files. func (rn *RootNode) excludeDirEntries(d *dirfdPlus, entries []fuse.DirEntry) (filtered []fuse.DirEntry) { - if rn.excluder == nil { + if rn.excluder == nil && rn.configPlainPath == "" { return entries } filtered = make([]fuse.DirEntry, 0, len(entries)) @@ -274,6 +274,7 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f PlaintextNames: args.plaintextnames, LongNames: args.longnames, ConfigCustom: args._configCustom, + Config: args.config, NoPrealloc: args.noprealloc, ForceOwner: args._forceOwner, Exclude: args.exclude, diff --git a/tests/reverse/config_custom_test.go b/tests/reverse/config_custom_test.go new file mode 100644 index 0000000..a7883af --- /dev/null +++ b/tests/reverse/config_custom_test.go @@ -0,0 +1,62 @@ +package reverse_test + +import ( + "os" + "os/exec" + "testing" + + "github.com/rfjakob/gocryptfs/v2/tests/test_helpers" +) + +// TestConfigCustomInsideCipherdir verifies that a custom config file (-config) +// located inside CIPHERDIR is hidden from the encrypted reverse view instead of +// leaking there in encrypted form. +// +// Regression test for https://github.com/rfjakob/gocryptfs/issues/1009 +func TestConfigCustomInsideCipherdir(t *testing.T) { + backingDir, err := os.MkdirTemp(test_helpers.TmpDir, t.Name()+".") + if err != nil { + t.Fatal(err) + } + // A regular file that must stay visible in the encrypted view. + if err := os.WriteFile(backingDir+"/secret.txt", []byte("hello"), 0600); err != nil { + t.Fatal(err) + } + // The custom config file lives *inside* the backing dir. + cfg := backingDir + "/my-custom.conf" + + // Init reverse fs with the config at the custom location. + initArgs := []string{"-q", "-init", "-reverse", "-extpass", "echo test", "-scryptn=10", "-config", cfg} + if plaintextnames { + initArgs = append(initArgs, "-plaintextnames") + } else if deterministic_names { + initArgs = append(initArgs, "-deterministic-names") + } + initArgs = append(initArgs, backingDir) + cmd := exec.Command(test_helpers.GocryptfsBinary, initArgs...) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + t.Fatalf("init failed: %v", err) + } + + mnt := backingDir + ".mnt" + sock := mnt + ".sock" + test_helpers.MountOrFatal(t, backingDir, mnt, "-reverse", "-extpass", "echo test", + "-config", cfg, "-ctlsock", sock) + defer test_helpers.UnmountPanic(mnt) + + // The custom config file must NOT show up (encrypted) in the view. + cCfg := ctlsockEncryptPath(t, sock, "my-custom.conf") + if test_helpers.VerifyExistence(t, mnt+"/"+cCfg) { + t.Errorf("custom config %q is exposed in the encrypted view (as %q), but should be hidden", cfg, cCfg) + } + // With a custom config file, no virtual gocryptfs.conf is presented. + if test_helpers.VerifyExistence(t, mnt+"/gocryptfs.conf") { + t.Errorf("gocryptfs.conf should not be present in the view when -config is used") + } + // Regular files must remain visible. + cSecret := ctlsockEncryptPath(t, sock, "secret.txt") + if !test_helpers.VerifyExistence(t, mnt+"/"+cSecret) { + t.Errorf("regular file secret.txt (as %q) is missing from the encrypted view", cSecret) + } +} |
