aboutsummaryrefslogtreecommitdiff
path: root/cmd/fscrypt
diff options
context:
space:
mode:
authorEric Biggers <ebiggers@google.com>2020-05-09 15:21:07 -0700
committerGitHub <noreply@github.com>2020-05-09 15:21:07 -0700
commit1cdefc21b8b07aad7aafeefd05d3124cf93b9216 (patch)
treeb5f304a4ecc101a5410bb2274d129dbc7dad6441 /cmd/fscrypt
parent338347ac4766f899fdc471d57f293798ff0e6c29 (diff)
parentde51add609bc74b7247ec4776bd694abbea24a45 (diff)
Merge pull request #217 from ebiggers/detect-incomplete-v1-locking
Try to detect incomplete locking of v1-encrypted directory
Diffstat (limited to 'cmd/fscrypt')
-rw-r--r--cmd/fscrypt/commands.go39
-rw-r--r--cmd/fscrypt/status.go21
2 files changed, 49 insertions, 11 deletions
diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go
index ec75584..51cf136 100644
--- a/cmd/fscrypt/commands.go
+++ b/cmd/fscrypt/commands.go
@@ -496,30 +496,55 @@ func lockAction(c *cli.Context) error {
if err = validateKeyringPrereqs(ctx, policy); 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.
+ // Check for permission to drop caches, if it may be needed.
if policy.NeedsUserKeyring() && dropCachesFlag.Value && !util.IsUserRoot() {
return newExitError(c, ErrDropCachesPerm)
}
if err = policy.Deprovision(allUsersFlag.Value); err != nil {
- return newExitError(c, err)
+ if err != keyring.ErrKeyNotPresent {
+ return newExitError(c, err)
+ }
+ // Key is no longer present. Normally that means the directory
+ // is already locked; in that case we exit with an error. But
+ // if the policy uses the user keyring (v1 policies only), then
+ // the directory might have been incompletely locked earlier,
+ // due to open files. Try to detect that case and finish
+ // locking the directory by dropping caches again.
+ if !policy.NeedsUserKeyring() || !isDirUnlockedHeuristic(path) {
+ log.Printf("policy %s is already fully deprovisioned", policy.Descriptor())
+ return newExitError(c, errors.Wrapf(ErrPolicyLocked, path))
+ }
}
if policy.NeedsUserKeyring() {
if err = dropCachesIfRequested(c, ctx); err != nil {
return newExitError(c, err)
}
+ if isDirUnlockedHeuristic(path) {
+ return newExitError(c, keyring.ErrKeyFilesOpen)
+ }
}
fmt.Fprintf(c.App.Writer, "%q is now locked.\n", path)
return nil
}
+// isDirUnlockedHeuristic returns true if we can create a subdirectory of the
+// given directory and therefore it is definitely still unlocked. It returns
+// false if the directory is probably locked (though it could also be unlocked).
+//
+// This is only useful if the directory's policy uses the user keyring, since
+// otherwise the status can be easily found via the filesystem keyring.
+func isDirUnlockedHeuristic(dirPath string) bool {
+ subdirPath := filepath.Join(dirPath, "fscrypt-is-dir-unlocked")
+ if err := os.Mkdir(subdirPath, 0700); err == nil {
+ os.Remove(subdirPath)
+ return true
+ }
+ return false
+}
+
// Purge removes all the policy keys from the keyring (also need unmount).
var Purge = cli.Command{
Name: "purge",
diff --git a/cmd/fscrypt/status.go b/cmd/fscrypt/status.go
index bf11495..40bb49e 100644
--- a/cmd/fscrypt/status.go
+++ b/cmd/fscrypt/status.go
@@ -66,8 +66,20 @@ func yesNoString(b bool) string {
return "No"
}
-func policyUnlockedStatus(policy *actions.Policy) string {
- switch policy.GetProvisioningStatus() {
+func policyUnlockedStatus(policy *actions.Policy, path string) string {
+ status := policy.GetProvisioningStatus()
+
+ // Due to a limitation in the old kernel API for fscrypt, for v1
+ // policies using the user keyring that are incompletely locked we'll
+ // get KeyAbsent, not KeyAbsentButFilesBusy as expected. If we have a
+ // directory path, use a heuristic to try to detect whether it is still
+ // usable and thus the policy is actually incompletely locked.
+ if status == keyring.KeyAbsent && policy.NeedsUserKeyring() &&
+ path != "" && isDirUnlockedHeuristic(path) {
+ status = keyring.KeyAbsentButFilesBusy
+ }
+
+ switch status {
case keyring.KeyPresent, keyring.KeyPresentButOnlyOtherUsers:
return "Yes"
case keyring.KeyAbsent:
@@ -174,7 +186,8 @@ func writeFilesystemStatus(w io.Writer, ctx *actions.Context) error {
continue
}
- fmt.Fprintf(t, "%s\t%s\t%s\n", descriptor, policyUnlockedStatus(policy),
+ fmt.Fprintf(t, "%s\t%s\t%s\n", descriptor,
+ policyUnlockedStatus(policy, ""),
strings.Join(policy.ProtectorDescriptors(), ", "))
}
return t.Flush()
@@ -194,7 +207,7 @@ func writePathStatus(w io.Writer, path string) error {
fmt.Fprintln(w)
fmt.Fprintf(w, "Policy: %s\n", policy.Descriptor())
fmt.Fprintf(w, "Options: %s\n", policy.Options())
- fmt.Fprintf(w, "Unlocked: %s\n", policyUnlockedStatus(policy))
+ fmt.Fprintf(w, "Unlocked: %s\n", policyUnlockedStatus(policy, path))
fmt.Fprintln(w)
options := policy.ProtectorOptions()