aboutsummaryrefslogtreecommitdiff
path: root/keyring
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
commit42e0dfe85ec7a75a2fa30c417d57eae60b5a881d (patch)
treeffa9fb08a5bbe51ec23bf219ef206ddc81799453 /keyring
parent2b25de6d445faefc28629603dd754aec9f744e60 (diff)
Keyring support for v2 encryption policies
Implement adding/removing v2 encryption policy keys to/from the kernel. The kernel requires that the new ioctls FS_IOC_ADD_ENCRYPTION_KEY and FS_IOC_REMOVE_ENCRYPTION_KEY be used for this. Root is not required. However, non-root support brings an extra complication: the kernel keeps track of which users have called FS_IOC_ADD_ENCRYPTION_KEY for the same key. FS_IOC_REMOVE_ENCRYPTION_KEY only works as one of these users, and it only removes the calling user's claim to the key; the key is only truly removed when the last claim is removed. Implement the following behavior: - 'fscrypt unlock' and pam_fscrypt add the key for the user, even if other user(s) have it added already. This behavior is needed so that another user can't remove the key out from under the user. - 'fscrypt lock' and pam_fscrypt remove the key for the user. However, if the key wasn't truly removed because other users still have it added, 'fscrypt lock' prints a warning. - 'fscrypt status' shows whether the directory is unlocked for anyone.
Diffstat (limited to 'keyring')
-rw-r--r--keyring/fs_keyring.go100
-rw-r--r--keyring/keyring.go35
-rw-r--r--keyring/keyring_test.go163
3 files changed, 282 insertions, 16 deletions
diff --git a/keyring/fs_keyring.go b/keyring/fs_keyring.go
index bed23af..970105e 100644
--- a/keyring/fs_keyring.go
+++ b/keyring/fs_keyring.go
@@ -37,6 +37,8 @@ import (
"github.com/google/fscrypt/crypto"
"github.com/google/fscrypt/filesystem"
+ "github.com/google/fscrypt/security"
+ "github.com/google/fscrypt/util"
)
var (
@@ -99,6 +101,8 @@ func buildKeySpecifier(spec *unix.FscryptKeySpecifier, descriptor string) error
switch len(descriptorBytes) {
case unix.FSCRYPT_KEY_DESCRIPTOR_SIZE:
spec.Type = unix.FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR
+ case unix.FSCRYPT_KEY_IDENTIFIER_SIZE:
+ spec.Type = unix.FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER
default:
return errors.Errorf("key descriptor %q has unknown length", descriptor)
}
@@ -106,6 +110,64 @@ func buildKeySpecifier(spec *unix.FscryptKeySpecifier, descriptor string) error
return nil
}
+type savedPrivs struct {
+ ruid, euid, suid int
+}
+
+// dropPrivsIfNeeded drops privileges (UIDs only) to the given user if we're
+// working with a v2 policy key, and if the user is different from the user the
+// process is currently running as.
+//
+// This is needed to change the effective UID so that FS_IOC_ADD_ENCRYPTION_KEY
+// and FS_IOC_REMOVE_ENCRYPTION_KEY will add/remove a claim to the key for the
+// intended user, and so that FS_IOC_GET_ENCRYPTION_KEY_STATUS will return the
+// correct status flags for the user.
+func dropPrivsIfNeeded(user *user.User, spec *unix.FscryptKeySpecifier) (*savedPrivs, error) {
+ if spec.Type == unix.FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR {
+ // v1 policy keys don't have any concept of user claims.
+ return nil, nil
+ }
+ targetUID := util.AtoiOrPanic(user.Uid)
+ ruid, euid, suid := security.GetUids()
+ if euid == targetUID {
+ return nil, nil
+ }
+ if err := security.SetUids(targetUID, targetUID, euid); err != nil {
+ return nil, err
+ }
+ return &savedPrivs{ruid, euid, suid}, nil
+}
+
+// restorePrivs restores root privileges if needed.
+func restorePrivs(privs *savedPrivs) error {
+ if privs != nil {
+ return security.SetUids(privs.ruid, privs.euid, privs.suid)
+ }
+ return nil
+}
+
+// validateKeyDescriptor validates that the correct key descriptor was provided.
+// This isn't really necessary; this is just an extra sanity check.
+func validateKeyDescriptor(spec *unix.FscryptKeySpecifier, descriptor string) (string, error) {
+ if spec.Type != unix.FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER {
+ // v1 policy key: the descriptor is chosen arbitrarily by
+ // userspace, so there's nothing to validate.
+ return descriptor, nil
+ }
+ // v2 policy key. The descriptor ("identifier" in the kernel UAPI) is
+ // calculated as a cryptographic hash of the key itself. The kernel
+ // ignores the provided value, and calculates and returns it itself. So
+ // verify that the returned value is as expected. If it's not, the key
+ // doesn't actually match the encryption policy we thought it was for.
+ actual := hex.EncodeToString(spec.U[:unix.FSCRYPT_KEY_IDENTIFIER_SIZE])
+ if descriptor == actual {
+ return descriptor, nil
+ }
+ return actual,
+ errors.Errorf("provided and actual key descriptors differ (%q != %q)",
+ descriptor, actual)
+}
+
// fsAddEncryptionKey adds the specified encryption key to the specified filesystem.
func fsAddEncryptionKey(key *crypto.Key, descriptor string,
mount *filesystem.Mount, user *user.User) error {
@@ -131,12 +193,22 @@ func fsAddEncryptionKey(key *crypto.Key, descriptor string,
arg.Raw_size = uint32(key.Len())
C.memcpy(raw, key.UnsafePtr(), C.size_t(key.Len()))
+ savedPrivs, err := dropPrivsIfNeeded(user, &arg.Key_spec)
+ if err != nil {
+ return err
+ }
_, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(),
unix.FS_IOC_ADD_ENCRYPTION_KEY, uintptr(argKey.UnsafePtr()))
+ restorePrivs(savedPrivs)
+
log.Printf("FS_IOC_ADD_ENCRYPTION_KEY(%q, %s, <raw>) = %v", mount.Path, descriptor, errno)
if errno != 0 {
return errors.Wrap(ErrKeyAdd, errno.Error())
}
+ if descriptor, err = validateKeyDescriptor(&arg.Key_spec, descriptor); err != nil {
+ fsRemoveEncryptionKey(descriptor, mount, user)
+ return err
+ }
return nil
}
@@ -156,17 +228,33 @@ func fsRemoveEncryptionKey(descriptor string, mount *filesystem.Mount,
return err
}
+ 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)))
+ 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)
switch errno {
case 0:
- if arg.Removal_status_flags&unix.FSCRYPT_KEY_REMOVAL_STATUS_FLAG_FILES_BUSY != 0 {
+ switch {
+ case arg.Removal_status_flags&unix.FSCRYPT_KEY_REMOVAL_STATUS_FLAG_OTHER_USERS != 0:
+ return ErrKeyAddedByOtherUsers
+ case arg.Removal_status_flags&unix.FSCRYPT_KEY_REMOVAL_STATUS_FLAG_FILES_BUSY != 0:
return ErrKeyFilesOpen
}
return nil
case unix.ENOKEY:
+ // 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
+ }
return ErrKeyNotPresent
default:
return errors.Wrap(ErrKeyRemove, errno.Error())
@@ -190,8 +278,14 @@ func fsGetEncryptionKeyStatus(descriptor string, mount *filesystem.Mount,
return KeyStatusUnknown, err
}
+ savedPrivs, err := dropPrivsIfNeeded(user, &arg.Key_spec)
+ if err != nil {
+ return KeyStatusUnknown, err
+ }
_, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(),
unix.FS_IOC_GET_ENCRYPTION_KEY_STATUS, uintptr(unsafe.Pointer(&arg)))
+ restorePrivs(savedPrivs)
+
log.Printf("FS_IOC_GET_ENCRYPTION_KEY_STATUS(%q, %s) = %v, status=%d, status_flags=0x%x",
mount.Path, descriptor, errno, arg.Status, arg.Status_flags)
if errno != 0 {
@@ -201,6 +295,10 @@ func fsGetEncryptionKeyStatus(descriptor string, mount *filesystem.Mount,
case unix.FSCRYPT_KEY_STATUS_ABSENT:
return KeyAbsent, nil
case unix.FSCRYPT_KEY_STATUS_PRESENT:
+ if arg.Key_spec.Type != unix.FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR &&
+ (arg.Status_flags&unix.FSCRYPT_KEY_STATUS_FLAG_ADDED_BY_SELF) == 0 {
+ return KeyPresentButOnlyOtherUsers, nil
+ }
return KeyPresent, nil
case unix.FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED:
return KeyAbsentButFilesBusy, nil
diff --git a/keyring/keyring.go b/keyring/keyring.go
index f0bd1b7..925d059 100644
--- a/keyring/keyring.go
+++ b/keyring/keyring.go
@@ -22,15 +22,18 @@
// keyring.go, and they delegate to either user_keyring.go or fs_keyring.go,
// depending on whether a user keyring or a filesystem keyring is being used.
//
+// v2 encryption policies always use the filesystem keyring.
// v1 policies use the user keyring by default, but can be configured to use the
// filesystem keyring instead (requires root and kernel v5.4+).
package keyring
import (
+ "encoding/hex"
"os/user"
"strconv"
"github.com/pkg/errors"
+ "golang.org/x/sys/unix"
"github.com/google/fscrypt/crypto"
"github.com/google/fscrypt/filesystem"
@@ -40,14 +43,15 @@ import (
// Keyring error values
var (
- ErrKeyAdd = util.SystemError("could not add key to the keyring")
- ErrKeyRemove = util.SystemError("could not remove key from the keyring")
- ErrKeyNotPresent = errors.New("key not present or already removed")
- ErrKeyFilesOpen = errors.New("some files using the key are still open")
- ErrKeySearch = errors.New("could not find key with descriptor")
- ErrSessionUserKeying = errors.New("user keyring not linked into session keyring")
- ErrAccessUserKeyring = errors.New("could not access user keyring")
- ErrLinkUserKeyring = util.SystemError("could not link user keyring into root keyring")
+ ErrKeyAdd = util.SystemError("could not add key to the keyring")
+ ErrKeyRemove = util.SystemError("could not remove key from the keyring")
+ ErrKeyNotPresent = errors.New("key not present or already removed")
+ ErrKeyFilesOpen = errors.New("some files using the key are still open")
+ ErrKeyAddedByOtherUsers = errors.New("other users have added the key too")
+ ErrKeySearch = errors.New("could not find key with descriptor")
+ ErrSessionUserKeying = errors.New("user keyring not linked into session keyring")
+ ErrAccessUserKeyring = errors.New("could not access user keyring")
+ ErrLinkUserKeyring = util.SystemError("could not link user keyring into root keyring")
)
// Options are the options which specify *which* keyring the key should be
@@ -69,9 +73,15 @@ type Options struct {
}
func shouldUseFsKeyring(descriptor string, options *Options) bool {
- // Use the filesystem keyring if use_fs_keyring_for_v1_policies is set
- // in /etc/fscrypt.conf and the kernel supports it.
- return options.UseFsKeyringForV1Policies && isFsKeyringSupported(options.Mount)
+ // For v1 encryption policy keys, use the filesystem keyring if
+ // use_fs_keyring_for_v1_policies is set in /etc/fscrypt.conf and the
+ // kernel supports it.
+ if len(descriptor) == hex.EncodedLen(unix.FSCRYPT_KEY_DESCRIPTOR_SIZE) {
+ return options.UseFsKeyringForV1Policies && isFsKeyringSupported(options.Mount)
+ }
+ // For v2 encryption policy keys, always use the filesystem keyring; the
+ // kernel doesn't support any other way.
+ return true
}
// AddEncryptionKey adds an encryption policy key to a kernel keyring. It uses
@@ -106,6 +116,7 @@ const (
KeyAbsent
KeyAbsentButFilesBusy
KeyPresent
+ KeyPresentButOnlyOtherUsers
)
func (status KeyStatus) String() string {
@@ -118,6 +129,8 @@ func (status KeyStatus) String() string {
return "AbsentButFilesBusy"
case KeyPresent:
return "Present"
+ case KeyPresentButOnlyOtherUsers:
+ return "PresentButOnlyOtherUsers"
default:
return strconv.Itoa(int(status))
}
diff --git a/keyring/keyring_test.go b/keyring/keyring_test.go
index 9a4570b..a675a70 100644
--- a/keyring/keyring_test.go
+++ b/keyring/keyring_test.go
@@ -19,6 +19,8 @@
package keyring
import (
+ "os/user"
+ "strconv"
"testing"
"golang.org/x/sys/unix"
@@ -50,6 +52,7 @@ var (
fakeValidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen)
fakeInvalidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen-1)
fakeV1Descriptor = "0123456789abcdef"
+ fakeV2Descriptor, _ = crypto.ComputeKeyDescriptor(fakeValidPolicyKey, 2)
)
func assertKeyStatus(t *testing.T, descriptor string, options *Options,
@@ -77,8 +80,8 @@ func getTestMount(t *testing.T) *filesystem.Mount {
return mount
}
-// getTestMountV2 is like getTestMount, but it also checks that the filesystem
-// keyring is supported.
+// getTestMountV2 is like getTestMount, but it also checks that the
+// filesystem keyring and v2 encryption policies are supported.
func getTestMountV2(t *testing.T) *filesystem.Mount {
mount := getTestMount(t)
if !isFsKeyringSupported(mount) {
@@ -93,9 +96,42 @@ func requireRoot(t *testing.T) {
}
}
+// getNonRootUsers checks for root permission, then returns the users for uids
+// 1000...1000+count-1. If this fails, the test is skipped.
+func getNonRootUsers(t *testing.T, count int) []*user.User {
+ requireRoot(t)
+ users := make([]*user.User, count)
+ for i := 0; i < count; i++ {
+ uid := 1000 + i
+ user, err := user.LookupId(strconv.Itoa(uid))
+ if err != nil {
+ t.Skip(err)
+ }
+ users[i] = user
+ }
+ return users
+}
+
+func getOptionsForFsKeyringUsers(t *testing.T, numNonRootUsers int) (rootOptions *Options, userOptions []*Options) {
+ mount := getTestMountV2(t)
+ nonRootUsers := getNonRootUsers(t, numNonRootUsers)
+ rootOptions = &Options{
+ Mount: mount,
+ User: testUser,
+ }
+ userOptions = make([]*Options, numNonRootUsers)
+ for i := 0; i < numNonRootUsers; i++ {
+ userOptions[i] = &Options{
+ Mount: mount,
+ User: nonRootUsers[i],
+ }
+ }
+ return
+}
+
// testAddAndRemoveKey does the common tests for adding+removing keys that are
-// run in multiple configurations (v1 policies with user keyring and v1 policies
-// with fs keyring).
+// run in multiple configurations (v1 policies with user keyring, v1 policies
+// with fs keyring, and v2 policies with fs keyring).
func testAddAndRemoveKey(t *testing.T, descriptor string, options *Options) {
// Basic add, get status, and remove
@@ -167,3 +203,122 @@ func TestFsKeyringV1PolicyKey(t *testing.T) {
}
testAddAndRemoveKey(t, fakeV1Descriptor, options)
}
+
+func TestV2PolicyKey(t *testing.T) {
+ mount := getTestMountV2(t)
+ options := &Options{
+ Mount: mount,
+ User: testUser,
+ }
+ testAddAndRemoveKey(t, fakeV2Descriptor, options)
+}
+
+func TestV2PolicyKeyCannotBeRemovedByAnotherUser(t *testing.T) {
+ rootOptions, userOptions := getOptionsForFsKeyringUsers(t, 2)
+ user1Options := userOptions[0]
+ user2Options := userOptions[1]
+
+ // Add key as non-root user.
+ if err := AddEncryptionKey(fakeValidPolicyKey, fakeV2Descriptor, user1Options); err != nil {
+ t.Error(err)
+ }
+ assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyPresent)
+ assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyPresentButOnlyOtherUsers)
+ assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers)
+
+ // Key shouldn't be removable by another user, even root.
+ err := RemoveEncryptionKey(fakeV2Descriptor, user2Options)
+ 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)
+ if err != ErrKeyAddedByOtherUsers {
+ t.Error(err)
+ }
+ assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyPresent)
+ assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyPresentButOnlyOtherUsers)
+ assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers)
+
+ if err := RemoveEncryptionKey(fakeV2Descriptor, user1Options); err != nil {
+ t.Error(err)
+ }
+ assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyAbsent)
+ assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyAbsent)
+ assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyAbsent)
+}
+
+func TestV2PolicyKeyMultipleUsers(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 as one user.
+ err := RemoveEncryptionKey(fakeV2Descriptor, user1Options)
+ if err != ErrKeyAddedByOtherUsers {
+ t.Error(err)
+ }
+ assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyPresentButOnlyOtherUsers)
+ assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyPresent)
+ assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyPresentButOnlyOtherUsers)
+
+ // Remove key as the other user.
+ err = RemoveEncryptionKey(fakeV2Descriptor, user2Options)
+ if err != nil {
+ t.Error(err)
+ }
+ assertKeyStatus(t, fakeV2Descriptor, user1Options, KeyAbsent)
+ assertKeyStatus(t, fakeV2Descriptor, user2Options, KeyAbsent)
+ assertKeyStatus(t, fakeV2Descriptor, rootOptions, KeyAbsent)
+}
+
+func TestV2PolicyKeyWrongDescriptor(t *testing.T) {
+ mount := getTestMountV2(t)
+ options := &Options{
+ Mount: mount,
+ User: testUser,
+ }
+ // one wrong but valid hex, and one not valid hex
+ wrongV2Descriptors := []string{"abcdabcdabcdabcdabcdabcdabcdabcd", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}
+
+ for _, desc := range wrongV2Descriptors {
+ if err := AddEncryptionKey(fakeValidPolicyKey, desc, options); err == nil {
+ RemoveEncryptionKey(desc, options)
+ t.Error("For v2 policy keys, AddEncryptionKey should fail if the descriptor is wrong")
+ }
+ }
+}
+
+func TestV2PolicyKeyBadMount(t *testing.T) {
+ options := &Options{
+ Mount: &filesystem.Mount{Path: "/NONEXISTENT_MOUNT"},
+ User: testUser,
+ }
+ if err := AddEncryptionKey(fakeValidPolicyKey, fakeV2Descriptor, options); err == nil {
+ RemoveEncryptionKey(fakeV2Descriptor, options)
+ t.Error("AddEncryptionKey should have failed with bad mount!")
+ }
+ if err := RemoveEncryptionKey(fakeV2Descriptor, options); err == nil {
+ t.Error("RemoveEncryptionKey should have failed with bad mount!")
+ }
+ status, err := GetEncryptionKeyStatus(fakeV2Descriptor, options)
+ if err == nil {
+ t.Error("GetEncryptionKeyStatus should have failed with bad mount!")
+ }
+ if status != KeyStatusUnknown {
+ t.Error("GetEncryptionKeyStatus should have returned unknown status!")
+ }
+}