diff options
Diffstat (limited to 'cmd/fscrypt')
| -rw-r--r-- | cmd/fscrypt/commands.go | 80 | ||||
| -rw-r--r-- | cmd/fscrypt/errors.go | 6 | ||||
| -rw-r--r-- | cmd/fscrypt/flags.go | 8 | ||||
| -rw-r--r-- | cmd/fscrypt/fscrypt.go | 33 |
4 files changed, 97 insertions, 30 deletions
diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go index 4f53fe2..e6c7f9a 100644 --- a/cmd/fscrypt/commands.go +++ b/cmd/fscrypt/commands.go @@ -25,12 +25,15 @@ import ( "log" "os" + "golang.org/x/sys/unix" + "github.com/pkg/errors" "github.com/urfave/cli" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" + "github.com/google/fscrypt/util" ) // Setup is a command which can to global or per-filesystem initialization. @@ -304,27 +307,36 @@ func unlockAction(c *cli.Context) error { var Purge = cli.Command{ Name: "purge", ArgsUsage: mountpointArg, - Usage: "[EXPERIMENTAL] remove a filesystem's keys", - Description: fmt.Sprintf(`EXPERIMENTAL: This command removes all the - policy keys for directories on %[1]s. This is intended to lock - all encrypted files and directories on %[1]s, in that unlocking + Usage: "Remove a filesystem's keys", + Description: fmt.Sprintf(`This command removes a user's policy keys for + directories on %[1]s. This is intended to lock all files and + directories encrypted by the user on %[1]s, in that unlocking them for reading will require providing a key again. However, - this action is currently subject to two significant limitations: - - (1) If "fscrypt purge" is run, but the filesystem has not yet - been unmounted, recently accessed encrypted directories and - files will remain accessible for some time. Because of this, - after purging a filesystem's keys, it is recommended to unmount - the filesystem. This limitation might be eliminated in a future - kernel version. - - (2) Even after unmounting the filesystem, the kernel may keep - contents of encrypted files cached in memory. This means direct - memory access (either though physical compromise or a kernel - exploit) could compromise encrypted data. This weakness can be - eliminated by cycling the power or mitigated by using page cache - and slab cache poisoning.`, mountpointArg), - Flags: []cli.Flag{forceFlag}, + there are four important things to note about this command: + + (1) When run with the default options, this command also clears + the dentry and inode cache, so that the encrypted files and + directories will no longer be visible. However, this requires + root privileges. + + (2) When run with %[2]s=false, the keyring is cleared and root + permissions are not required, but recently accessed encrypted + directories and files will remain cached for some time. Because + of this, after purging a filesystem's keys, it is recommended to + unmount the filesystem. + + (3) When run as root, this command removes the policy keys for + all users. However, this will only work if the PAM module has + been enabled. Otherwise, only root's keys may be removed. + + (4) Even after unmounting the filesystem or clearing the + caches, the kernel may keep contents of files in memory. This + means direct memory access (either though physical compromise or + a kernel exploit) could compromise encrypted data. This weakness + can be eliminated by cycling the power or mitigated by using + page cache and slab cache poisoning.`, mountpointArg, + shortDisplay(dropCachesFlag)), + Flags: []cli.Flag{forceFlag, dropCachesFlag}, Action: purgeAction, } @@ -333,25 +345,39 @@ func purgeAction(c *cli.Context) error { return expectedArgsErr(c, 1, false) } + if dropCachesFlag.Value { + if unix.Geteuid() != 0 { + return newExitError(c, ErrDropCachesPerm) + } + } + ctx, err := actions.NewContextFromMountpoint(c.Args().Get(0)) if err != nil { return newExitError(c, err) } - err = askConfirmation(fmt.Sprintf( - "Purge all policy keys from %q?", - ctx.Mount.Path), false, - "Encrypted data on this filesystem will be inaccessible until unlocked again!!") - if err != nil { + question := fmt.Sprintf("Purge all policy keys from %q", ctx.Mount.Path) + if dropCachesFlag.Value { + question += " and drop global inode cache" + } + warning := "Encrypted data on this filesystem will be inaccessible until unlocked again!!" + if err = askConfirmation(question+"?", false, warning); err != nil { return newExitError(c, err) } if err = actions.PurgeAllPolicies(ctx); err != nil { return newExitError(c, err) } + fmt.Fprintf(c.App.Writer, "Policies purged for %q.\n", ctx.Mount.Path) - fmt.Fprintf(c.App.Writer, "All keys purged for %q.\n", ctx.Mount.Path) - fmt.Fprintf(c.App.Writer, "Filesystem %q should now be unmounted.\n", ctx.Mount.Path) + if dropCachesFlag.Value { + if err = util.DropInodeCache(); err != nil { + return newExitError(c, err) + } + fmt.Fprintf(c.App.Writer, "Global inode cache cleared.\n") + } else { + fmt.Fprintf(c.App.Writer, "Filesystem %q should now be unmounted.\n", ctx.Mount.Path) + } return nil } diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go index b0548d5..10dbf1e 100644 --- a/cmd/fscrypt/errors.go +++ b/cmd/fscrypt/errors.go @@ -58,6 +58,7 @@ var ( ErrNotEmptyDir = errors.New("not an empty directory") ErrNotPassphrase = errors.New("protector does not use a passphrase") ErrUnknownUser = errors.New("unknown user") + ErrDropCachesPerm = errors.New("inode cache can only be dropped as root") ) var loadHelpText = fmt.Sprintf("You may need to mount a linked filesystem. Run with %s for more information.", shortDisplay(verboseFlag)) @@ -112,6 +113,11 @@ func getErrorSuggestions(err error) string { cannot be encrypted in-place. Instead, encrypt an empty directory, copy the files into that encrypted directory, and securely delete the originals with "shred".` + case ErrDropCachesPerm: + return fmt.Sprintf(`Either this command should be run as root to + properly clear the inode cache, or it should be run with + %s=false (this may leave encrypted files and directories + in an accessible state).`, shortDisplay(dropCachesFlag)) case ErrAllLoadsFailed: return loadHelpText default: diff --git a/cmd/fscrypt/flags.go b/cmd/fscrypt/flags.go index d54a3bd..a06b952 100644 --- a/cmd/fscrypt/flags.go +++ b/cmd/fscrypt/flags.go @@ -158,6 +158,14 @@ var ( "fscrypt unlock" will need to be run in order to use the directory.`, } + dropCachesFlag = &boolFlag{ + Name: "drop-caches", + Usage: `After purging the keys from the keyring, drop the + inode and dentry cache for the purge to take effect. + Without this flag, cached encrypted files may still have + their plaintext visible. Requires root privileges.`, + Default: true, + } ) // Option flags: used to specify options instead of being prompted for them diff --git a/cmd/fscrypt/fscrypt.go b/cmd/fscrypt/fscrypt.go index fc93c05..fe1e0c9 100644 --- a/cmd/fscrypt/fscrypt.go +++ b/cmd/fscrypt/fscrypt.go @@ -28,8 +28,12 @@ import ( "io/ioutil" "log" "os" + "strconv" "time" + "golang.org/x/sys/unix" + + "github.com/google/fscrypt/security" "github.com/urfave/cli" ) @@ -99,7 +103,7 @@ func setupCommand(command *cli.Command) { // Setup function handlers command.OnUsageError = onUsageError if len(command.Subcommands) == 0 { - command.Before = setupOutputs + command.Before = setupBefore } else { // Cleanup subcommands (if applicable) for i := range command.Subcommands { @@ -108,10 +112,12 @@ func setupCommand(command *cli.Command) { } } -// setupOutputs makes sure our logs, errors, and output are going to the correct +// setupBefore makes sure our logs, errors, and output are going to the correct // io.Writers and that we haven't over-specified our flags. We only print the // logs when using verbose, and only print normal stuff when not using quiet. -func setupOutputs(c *cli.Context) error { +// When running with sudo, this function also verifies that we have the proper +// keyring linkage enabled. +func setupBefore(c *cli.Context) error { log.SetOutput(ioutil.Discard) c.App.Writer = ioutil.Discard @@ -121,6 +127,27 @@ func setupOutputs(c *cli.Context) error { if !quietFlag.Value { c.App.Writer = os.Stdout } + + if unix.Geteuid() != 0 { + return nil // Must be root to setup links + } + euid, err := strconv.Atoi(os.Getenv("SUDO_UID")) + if err != nil { + return nil // Must be running with sudo + } + egid, err := strconv.Atoi(os.Getenv("SUDO_GID")) + if err != nil { + return nil // Must be running with sudo + } + + // Dropping and raising privileges checks the needed keyring link. + privs, err := security.DropThreadPrivileges(euid, egid) + if err != nil { + return newExitError(c, err) + } + if err := security.RaiseThreadPrivileges(privs); err != nil { + return newExitError(c, err) + } return nil } |