aboutsummaryrefslogtreecommitdiff
path: root/cmd/fscrypt
diff options
context:
space:
mode:
authorJoe Richey joerichey@google.com <joerichey@google.com>2017-08-15 18:11:29 -0700
committerJoe Richey joerichey@google.com <joerichey@google.com>2017-08-17 22:49:44 -0700
commit151e8965fa3a9c8f65e316430f9df0fa763fb02d (patch)
tree5be6cb1e1d617e60ba7624abc3c940c65715ba5e /cmd/fscrypt
parentb4d51e0f4d34dbfd78e23662f3dfd90e86ae5e48 (diff)
cmd/fscrypt: purge command now clears cache
Diffstat (limited to 'cmd/fscrypt')
-rw-r--r--cmd/fscrypt/commands.go80
-rw-r--r--cmd/fscrypt/errors.go6
-rw-r--r--cmd/fscrypt/flags.go8
-rw-r--r--cmd/fscrypt/fscrypt.go33
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
}