diff options
| author | Eric Biggers <ebiggers@google.com> | 2019-12-15 19:31:39 -0800 |
|---|---|---|
| committer | Eric Biggers <ebiggers@google.com> | 2020-01-05 10:02:13 -0800 |
| commit | 068879664efd8a0f983cbc3e8115571047fe9edd (patch) | |
| tree | 51019d4d215c2c61b848b2aeaf7b2027952e65e8 | |
| parent | 42e0dfe85ec7a75a2fa30c417d57eae60b5a881d (diff) | |
cmd/fscrypt, keyring: add --all-users option to 'fscrypt lock'
Allow root to provide the --all-users option to 'fscrypt lock' to force
an encryption key to be removed from the filesystem (i.e., force an
encrypted directory to be locked), even if other users have added it.
To implement this option, we just need to use the
FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS ioctl rather than
FS_IOC_REMOVE_ENCRYPTION_KEY.
In theory this option could be implemented for the user keyrings case
too, but it would be difficult and the user keyrings are being
deprecated for fscrypt, so don't bother.
| -rw-r--r-- | actions/policy.go | 6 | ||||
| -rw-r--r-- | cmd/fscrypt/commands.go | 8 | ||||
| -rw-r--r-- | cmd/fscrypt/errors.go | 3 | ||||
| -rw-r--r-- | cmd/fscrypt/flags.go | 10 | ||||
| -rw-r--r-- | keyring/fs_keyring.go | 29 | ||||
| -rw-r--r-- | keyring/keyring.go | 8 | ||||
| -rw-r--r-- | keyring/keyring_test.go | 50 | ||||
| -rw-r--r-- | pam_fscrypt/pam_fscrypt.go | 2 |
8 files changed, 82 insertions, 34 deletions
diff --git a/actions/policy.go b/actions/policy.go index f448620..41e108e 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -56,7 +56,7 @@ func PurgeAllPolicies(ctx *Context) error { } for _, policyDescriptor := range policies { - err = keyring.RemoveEncryptionKey(policyDescriptor, ctx.getKeyringOptions()) + err = keyring.RemoveEncryptionKey(policyDescriptor, ctx.getKeyringOptions(), false) switch errors.Cause(err) { case nil, keyring.ErrKeyNotPresent: // We don't care if the key has already been removed @@ -416,9 +416,9 @@ func (policy *Policy) Provision() error { // Deprovision removes the Policy key from the kernel keyring. This prevents // reading and writing to the directory --- unless the target keyring is a user // keyring, in which case caches must be dropped too. -func (policy *Policy) Deprovision() error { +func (policy *Policy) Deprovision(allUsers bool) error { return keyring.RemoveEncryptionKey(policy.Descriptor(), - policy.Context.getKeyringOptions()) + policy.Context.getKeyringOptions(), allUsers) } // NeedsUserKeyring returns true if Provision and Deprovision for this policy diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go index 0bf0a4c..41009b0 100644 --- a/cmd/fscrypt/commands.go +++ b/cmd/fscrypt/commands.go @@ -232,7 +232,7 @@ func encryptPath(path string) (err error) { defer func() { policy.Lock() if err != nil { - policy.Deprovision() + policy.Deprovision(false) policy.Revert() } }() @@ -248,7 +248,7 @@ func encryptPath(path string) (err error) { return } if skipUnlockFlag.Value { - defer policy.Deprovision() + defer policy.Deprovision(false) } } if err = policy.Apply(path); os.IsPermission(errors.Cause(err)) { @@ -426,7 +426,7 @@ var Lock = cli.Command{ 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}, + Flags: []cli.Flag{dropCachesFlag, userFlag, allUsersFlag}, Action: lockAction, } @@ -465,7 +465,7 @@ func lockAction(c *cli.Context) error { return newExitError(c, ErrDropCachesPerm) } - if err = policy.Deprovision(); err != nil { + if err = policy.Deprovision(allUsersFlag.Value); err != nil { return newExitError(c, err) } diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go index ba9ec7a..5239155 100644 --- a/cmd/fscrypt/errors.go +++ b/cmd/fscrypt/errors.go @@ -103,7 +103,8 @@ func getErrorSuggestions(err error) string { re-running 'fscrypt lock'.` case keyring.ErrKeyAddedByOtherUsers: return `Directory couldn't be fully locked because other user(s) - have unlocked it.` + have unlocked it. If you want to force the directory to + be locked, use 'sudo fscrypt lock --all-users DIR'.` 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/flags.go b/cmd/fscrypt/flags.go index a22ec05..b7933c9 100644 --- a/cmd/fscrypt/flags.go +++ b/cmd/fscrypt/flags.go @@ -116,7 +116,7 @@ var ( allFlags = []prettyFlag{helpFlag, versionFlag, verboseFlag, quietFlag, forceFlag, legacyFlag, skipUnlockFlag, timeTargetFlag, sourceFlag, nameFlag, keyFileFlag, protectorFlag, - unlockWithFlag, policyFlag} + unlockWithFlag, policyFlag, allUsersFlag} // universalFlags contains flags that should be on every command universalFlags = []cli.Flag{verboseFlag, quietFlag, helpFlag} ) @@ -170,6 +170,14 @@ var ( privileges.`, Default: true, } + allUsersFlag = &boolFlag{ + Name: "all-users", + Usage: `Lock the directory no matter which user(s) have unlocked + it. Requires root privileges. This flag is only + necessary if the directory was unlocked by a user + different from the one you're locking it as. This flag + is only implemented for v2 encryption policies.`, + } ) // Option flags: used to specify options instead of being prompted for them diff --git a/keyring/fs_keyring.go b/keyring/fs_keyring.go index 970105e..42c1648 100644 --- a/keyring/fs_keyring.go +++ b/keyring/fs_keyring.go @@ -228,16 +228,23 @@ func fsRemoveEncryptionKey(descriptor string, mount *filesystem.Mount, return err } - savedPrivs, err := dropPrivsIfNeeded(user, &arg.Key_spec) - if err != nil { - return err + ioc := unix.FS_IOC_REMOVE_ENCRYPTION_KEY + iocName := "FS_IOC_REMOVE_ENCRYPTION_KEY" + var savedPrivs *savedPrivs + if user == nil { + ioc = unix.FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS + iocName = "FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS" + } else { + savedPrivs, err = dropPrivsIfNeeded(user, &arg.Key_spec) + if err != nil { + return err + } } - _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(), - unix.FS_IOC_REMOVE_ENCRYPTION_KEY, uintptr(unsafe.Pointer(&arg))) + _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(), uintptr(ioc), uintptr(unsafe.Pointer(&arg))) restorePrivs(savedPrivs) - log.Printf("FS_IOC_REMOVE_ENCRYPTION_KEY(%q, %s) = %v, removal_status_flags=0x%x", - mount.Path, descriptor, errno, arg.Removal_status_flags) + log.Printf("%s(%q, %s) = %v, removal_status_flags=0x%x", + iocName, mount.Path, descriptor, errno, arg.Removal_status_flags) switch errno { case 0: switch { @@ -251,9 +258,11 @@ func fsRemoveEncryptionKey(descriptor string, mount *filesystem.Mount, // ENOKEY means either the key is completely missing or that the // current user doesn't have a claim to it. Distinguish between // these two cases by getting the key status. - status, _ := fsGetEncryptionKeyStatus(descriptor, mount, user) - if status == KeyPresentButOnlyOtherUsers { - return ErrKeyAddedByOtherUsers + if user != nil { + status, _ := fsGetEncryptionKeyStatus(descriptor, mount, user) + if status == KeyPresentButOnlyOtherUsers { + return ErrKeyAddedByOtherUsers + } } return ErrKeyNotPresent default: diff --git a/keyring/keyring.go b/keyring/keyring.go index 925d059..5a75153 100644 --- a/keyring/keyring.go +++ b/keyring/keyring.go @@ -100,9 +100,13 @@ func AddEncryptionKey(key *crypto.Key, descriptor string, options *Options) erro // RemoveEncryptionKey removes an encryption policy key from a kernel keyring. // It uses either the filesystem keyring for the target Mount or the user // keyring for the target User. -func RemoveEncryptionKey(descriptor string, options *Options) error { +func RemoveEncryptionKey(descriptor string, options *Options, allUsers bool) error { if shouldUseFsKeyring(descriptor, options) { - return fsRemoveEncryptionKey(descriptor, options.Mount, options.User) + user := options.User + if allUsers { + user = nil + } + return fsRemoveEncryptionKey(descriptor, options.Mount, user) } return userRemoveKey(options.Service+descriptor, options.User) } diff --git a/keyring/keyring_test.go b/keyring/keyring_test.go index a675a70..8912556 100644 --- a/keyring/keyring_test.go +++ b/keyring/keyring_test.go @@ -139,11 +139,11 @@ func testAddAndRemoveKey(t *testing.T, descriptor string, options *Options) { t.Error(err) } assertKeyStatus(t, descriptor, options, KeyPresent) - if err := RemoveEncryptionKey(descriptor, options); err != nil { + if err := RemoveEncryptionKey(descriptor, options, false); err != nil { t.Error(err) } assertKeyStatus(t, descriptor, options, KeyAbsent) - err := RemoveEncryptionKey(descriptor, options) + err := RemoveEncryptionKey(descriptor, options, false) if err != ErrKeyNotPresent { t.Error(err) } @@ -155,12 +155,12 @@ func testAddAndRemoveKey(t *testing.T, descriptor string, options *Options) { if err := AddEncryptionKey(fakeValidPolicyKey, descriptor, options); err != nil { t.Error("AddEncryptionKey should not fail if key already exists") } - RemoveEncryptionKey(descriptor, options) + RemoveEncryptionKey(descriptor, options, false) assertKeyStatus(t, descriptor, options, KeyAbsent) // Adding a key with wrong length should fail if err := AddEncryptionKey(fakeInvalidPolicyKey, descriptor, options); err == nil { - RemoveEncryptionKey(descriptor, options) + RemoveEncryptionKey(descriptor, options, false) t.Error("AddEncryptionKey should fail with wrong-length key") } assertKeyStatus(t, descriptor, options, KeyAbsent) @@ -227,14 +227,14 @@ func TestV2PolicyKeyCannotBeRemovedByAnotherUser(t *testing.T) { assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) // Key shouldn't be removable by another user, even root. - err := RemoveEncryptionKey(fakeV2Descriptor, user2Options) + err := RemoveEncryptionKey(fakeV2Descriptor, user2Options, false) if err != ErrKeyAddedByOtherUsers { t.Error(err) } assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyPresent) assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyPresentButOnlyOtherUsers) assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) - err = RemoveEncryptionKey(fakeV2Descriptor, rootOptions) + err = RemoveEncryptionKey(fakeV2Descriptor, rootOptions, false) if err != ErrKeyAddedByOtherUsers { t.Error(err) } @@ -242,7 +242,7 @@ func TestV2PolicyKeyCannotBeRemovedByAnotherUser(t *testing.T) { assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyPresentButOnlyOtherUsers) assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) - if err := RemoveEncryptionKey(fakeV2Descriptor, user1Options); err != nil { + if err := RemoveEncryptionKey(fakeV2Descriptor, user1Options, false); err != nil { t.Error(err) } assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyAbsent) @@ -267,7 +267,7 @@ func TestV2PolicyKeyMultipleUsers(t *testing.T) { assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) // Remove key as one user. - err := RemoveEncryptionKey(fakeV2Descriptor, user1Options) + err := RemoveEncryptionKey(fakeV2Descriptor, user1Options, false) if err != ErrKeyAddedByOtherUsers { t.Error(err) } @@ -276,7 +276,7 @@ func TestV2PolicyKeyMultipleUsers(t *testing.T) { assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) // Remove key as the other user. - err = RemoveEncryptionKey(fakeV2Descriptor, user2Options) + err = RemoveEncryptionKey(fakeV2Descriptor, user2Options, false) if err != nil { t.Error(err) } @@ -296,7 +296,7 @@ func TestV2PolicyKeyWrongDescriptor(t *testing.T) { for _, desc := range wrongV2Descriptors { if err := AddEncryptionKey(fakeValidPolicyKey, desc, options); err == nil { - RemoveEncryptionKey(desc, options) + RemoveEncryptionKey(desc, options, false) t.Error("For v2 policy keys, AddEncryptionKey should fail if the descriptor is wrong") } } @@ -308,10 +308,10 @@ func TestV2PolicyKeyBadMount(t *testing.T) { User: testUser, } if err := AddEncryptionKey(fakeValidPolicyKey, fakeV2Descriptor, options); err == nil { - RemoveEncryptionKey(fakeV2Descriptor, options) + RemoveEncryptionKey(fakeV2Descriptor, options, false) t.Error("AddEncryptionKey should have failed with bad mount!") } - if err := RemoveEncryptionKey(fakeV2Descriptor, options); err == nil { + if err := RemoveEncryptionKey(fakeV2Descriptor, options, false); err == nil { t.Error("RemoveEncryptionKey should have failed with bad mount!") } status, err := GetEncryptionKeyStatus(fakeV2Descriptor, options) @@ -322,3 +322,29 @@ func TestV2PolicyKeyBadMount(t *testing.T) { t.Error("GetEncryptionKeyStatus should have returned unknown status!") } } + +func TestV2PolicyKeyRemoveForAllUsers(t *testing.T) { + rootOptions, userOptions := getOptionsForFsKeyringUsers(t, 2) + user1Options := userOptions[0] + user2Options := userOptions[1] + + // Add key as two non-root users. + if err := AddEncryptionKey(fakeValidPolicyKey, fakeV2Descriptor, user1Options); err != nil { + t.Error(err) + } + if err := AddEncryptionKey(fakeValidPolicyKey, fakeV2Descriptor, user2Options); err != nil { + t.Error(err) + } + assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyPresent) + assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyPresent) + assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers) + + // Remove key for all users as root. + err := RemoveEncryptionKey(fakeV2Descriptor, rootOptions, true) + if err != nil { + t.Error(err) + } + assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyAbsent) + assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyAbsent) + assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyAbsent) +} diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index 5f573e3..a7582cc 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -266,7 +266,7 @@ func lockLoginPolicies(handle *pam.Handle) error { if err := beginProvisioningOp(handle, policy); err != nil { return err } - deprovisionErr := policy.Deprovision() + deprovisionErr := policy.Deprovision(false) if err := endProvisioningOp(handle, policy); err != nil { return err } |