aboutsummaryrefslogtreecommitdiff
path: root/cmd/fscrypt
diff options
context:
space:
mode:
authorEric Biggers <ebiggers@google.com>2019-12-15 19:31:39 -0800
committerEric Biggers <ebiggers@google.com>2020-01-05 10:02:13 -0800
commit9003a0331a112e8901fae8279f4897a825ee8069 (patch)
tree37f6341d853346489f69ebd8b671b8133e69dfd5 /cmd/fscrypt
parent6ffc9457945a9484d2757cc4b01de35426502d0a (diff)
cmd/fscrypt: add 'fscrypt lock' command
Add support for 'fscrypt lock'. This command "locks" a directory, undoing 'fscrypt unlock'. When the filesystem keyring is used, 'fscrypt lock' also detects when a directory wasn't fully locked due to some files still being in-use. It can then be run again later to try to finish locking the files.
Diffstat (limited to 'cmd/fscrypt')
-rw-r--r--cmd/fscrypt/commands.go95
-rw-r--r--cmd/fscrypt/errors.go6
-rw-r--r--cmd/fscrypt/fscrypt.go2
3 files changed, 93 insertions, 10 deletions
diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go
index a3bfef2..8f2d21b 100644
--- a/cmd/fscrypt/commands.go
+++ b/cmd/fscrypt/commands.go
@@ -281,8 +281,8 @@ var Unlock = cli.Command{
appropriate key into the keyring. This requires unlocking one of
the protectors protecting this directory (either by selecting a
protector or specifying one with %s). This directory will be
- locked again upon reboot, or after running "fscrypt purge" and
- unmounting the corresponding filesystem.`, directoryArg,
+ locked again upon reboot, or after running "fscrypt lock" or
+ "fscrypt purge".`, directoryArg,
shortDisplay(unlockWithFlag)),
Flags: []cli.Flag{unlockWithFlag, keyFileFlag, userFlag},
Action: unlockAction,
@@ -328,6 +328,88 @@ func unlockAction(c *cli.Context) error {
return nil
}
+func dropCachesIfRequested(c *cli.Context, ctx *actions.Context) error {
+ if dropCachesFlag.Value {
+ if err := security.DropFilesystemCache(); err != nil {
+ return err
+ }
+ fmt.Fprintf(c.App.Writer, "Encrypted data removed from filesystem cache.\n")
+ } else {
+ fmt.Fprintf(c.App.Writer, "Filesystem %q should now be unmounted.\n", ctx.Mount.Path)
+ }
+ return nil
+}
+
+// Lock takes an encrypted directory and locks it, undoing Unlock.
+var Lock = cli.Command{
+ Name: "lock",
+ ArgsUsage: directoryArg,
+ Usage: "lock an encrypted directory",
+ Description: fmt.Sprintf(`This command takes %s, an encrypted directory
+ which has been unlocked by fscrypt, and locks the directory by
+ removing the encryption key from the kernel. I.e., it undoes the
+ effect of 'fscrypt unlock'.
+
+ For this to be effective, all files in the directory must first
+ be closed.
+
+ The %s=true option may be needed to properly lock the directory.
+ Root is required for this.
+
+ WARNING: even after the key has been removed, decrypted data may
+ still be present in freed memory, where it may still be
+ recoverable by an attacker who compromises system memory. To be
+ fully safe, you must reboot with a power cycle.`,
+ directoryArg, shortDisplay(dropCachesFlag)),
+ Flags: []cli.Flag{dropCachesFlag, userFlag},
+ Action: lockAction,
+}
+
+func lockAction(c *cli.Context) error {
+ if c.NArg() != 1 {
+ return expectedArgsErr(c, 1, false)
+ }
+
+ targetUser, err := parseUserFlag(true)
+ if err != nil {
+ return newExitError(c, err)
+ }
+ path := c.Args().Get(0)
+ ctx, err := actions.NewContextFromPath(path, targetUser)
+ if err != nil {
+ return newExitError(c, err)
+ }
+
+ log.Printf("performing sanity checks")
+ // Ensure path is encrypted and filesystem is using fscrypt.
+ policy, err := actions.GetPolicyFromPath(ctx, path)
+ if err != nil {
+ return newExitError(c, err)
+ }
+ // Check if directory is already locked
+ if policy.IsFullyDeprovisioned() {
+ log.Printf("policy %s is already fully deprovisioned", policy.Descriptor())
+ return newExitError(c, errors.Wrapf(ErrPolicyLocked, path))
+ }
+ // Check for permission to drop caches, if it will be needed.
+ if policy.NeedsUserKeyring() && dropCachesFlag.Value && !util.IsUserRoot() {
+ return newExitError(c, ErrDropCachesPerm)
+ }
+
+ if err = policy.Deprovision(); err != nil {
+ return newExitError(c, err)
+ }
+
+ if policy.NeedsUserKeyring() {
+ if err = dropCachesIfRequested(c, ctx); err != nil {
+ return newExitError(c, err)
+ }
+ }
+
+ fmt.Fprintf(c.App.Writer, "%q is now locked.\n", path)
+ return nil
+}
+
// Purge removes all the policy keys from the keyring (also need unmount).
var Purge = cli.Command{
Name: "purge",
@@ -401,13 +483,8 @@ func purgeAction(c *cli.Context) error {
}
fmt.Fprintf(c.App.Writer, "Policies purged for %q.\n", ctx.Mount.Path)
- if dropCachesFlag.Value {
- if err = security.DropFilesystemCache(); err != nil {
- return newExitError(c, err)
- }
- fmt.Fprintf(c.App.Writer, "Encrypted data removed from filesystem cache.\n")
- } else {
- fmt.Fprintf(c.App.Writer, "Filesystem %q should now be unmounted.\n", ctx.Mount.Path)
+ if err = dropCachesIfRequested(c, ctx); err != nil {
+ return newExitError(c, err)
}
return nil
}
diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go
index ed57dbe..68135fe 100644
--- a/cmd/fscrypt/errors.go
+++ b/cmd/fscrypt/errors.go
@@ -56,6 +56,7 @@ var (
ErrAllLoadsFailed = errors.New("could not load any protectors")
ErrMustBeRoot = errors.New("this command must be run as root")
ErrPolicyUnlocked = errors.New("this file or directory is already unlocked")
+ ErrPolicyLocked = errors.New("this file or directory is already locked")
ErrBadOwners = errors.New("you do not own this directory")
ErrNotEmptyDir = errors.New("not an empty directory")
ErrNotPassphrase = errors.New("protector does not use a passphrase")
@@ -94,6 +95,11 @@ func getErrorSuggestions(err error) string {
needs to be enabled for this filesystem. See the
documentation on how to enable encryption on ext4
systems (and the risks of doing so).`
+ case keyring.ErrKeyFilesOpen:
+ return `Directory was incompletely locked because some files are
+ still open. These files remain accessible. Try killing
+ any processes using files in the directory, then
+ re-running 'fscrypt lock'.`
case keyring.ErrSessionUserKeying:
return `This is usually the result of a bad PAM configuration.
Either correct the problem in your PAM stack, enable
diff --git a/cmd/fscrypt/fscrypt.go b/cmd/fscrypt/fscrypt.go
index 9ac8e2f..b6549f4 100644
--- a/cmd/fscrypt/fscrypt.go
+++ b/cmd/fscrypt/fscrypt.go
@@ -76,7 +76,7 @@ func main() {
// Initialize command list and setup all of the commands.
app.Action = defaultAction
- app.Commands = []cli.Command{Setup, Encrypt, Unlock, Purge, Status, Metadata}
+ app.Commands = []cli.Command{Setup, Encrypt, Unlock, Lock, Purge, Status, Metadata}
for i := range app.Commands {
setupCommand(&app.Commands[i])
}