From 6ffc9457945a9484d2757cc4b01de35426502d0a Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: keyring: support filesystem keyring with v1 encryption policies Linux v5.4 and later allows fscrypt keys to be added/removed directly to/from the filesystem via the new ioctls FS_IOC_ADD_ENCRYPTION_KEY and FS_IOC_REMOVE_ENCRYPTION_KEY. Among other benefits, these fix the key visibility problems that many users have been running into, where system services and containers can't access encrypted files. Allow the user to opt-in to using these new ioctls for their existing encrypted directories by setting in their /etc/fscrypt.conf: "use_fs_keyring_for_v1_policies": true Note that it can't really be on by default, since for v1 policies the ioctls require root, whereas user keyrings don't. I.e., setting this to true means that users will need to use 'sudo fscrypt unlock', not 'fscrypt unlock'. v2 policies won't have this restriction. --- keyring/fs_keyring.go | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 keyring/fs_keyring.go (limited to 'keyring/fs_keyring.go') diff --git a/keyring/fs_keyring.go b/keyring/fs_keyring.go new file mode 100644 index 0000000..bed23af --- /dev/null +++ b/keyring/fs_keyring.go @@ -0,0 +1,211 @@ +/* + * fs_keyring.go - Add/remove encryption policy keys to/from filesystem + * + * Copyright 2019 Google LLC + * Author: Eric Biggers (ebiggers@google.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package keyring + +/* +#include +*/ +import "C" + +import ( + "encoding/hex" + "log" + "os" + "os/user" + "sync" + "unsafe" + + "github.com/pkg/errors" + "golang.org/x/sys/unix" + + "github.com/google/fscrypt/crypto" + "github.com/google/fscrypt/filesystem" +) + +var ( + fsKeyringSupported bool + fsKeyringSupportedKnown bool + fsKeyringSupportedLock sync.Mutex +) + +func checkForFsKeyringSupport(mount *filesystem.Mount) bool { + dir, err := os.Open(mount.Path) + if err != nil { + log.Printf("Unexpected error opening %q. Assuming filesystem keyring is unsupported.", + mount.Path) + return false + } + defer dir.Close() + + // FS_IOC_ADD_ENCRYPTION_KEY with a NULL argument will fail with ENOTTY + // if the ioctl isn't supported. Otherwise it should fail with EFAULT. + // + // Note that there's no need to check for FS_IOC_REMOVE_ENCRYPTION_KEY + // support separately, since it's guaranteed to be available if + // FS_IOC_ADD_ENCRYPTION_KEY is. There's also no need to check for + // support on every filesystem separately, since either the kernel + // supports the ioctls on all fscrypt-capable filesystems or it doesn't. + _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(), unix.FS_IOC_ADD_ENCRYPTION_KEY, 0) + if errno == unix.ENOTTY { + log.Printf("Kernel doesn't support filesystem keyring. Falling back to user keyring.") + return false + } + if errno == unix.EFAULT { + log.Printf("Detected support for filesystem keyring") + } else { + // EFAULT is expected, but as long as we didn't get ENOTTY the + // ioctl should be available. + log.Printf("Unexpected error from FS_IOC_ADD_ENCRYPTION_KEY(%q, NULL): %v", mount.Path, errno) + } + return true +} + +// isFsKeyringSupported returns true if the kernel supports the ioctls to +// add/remove fscrypt keys directly to/from the filesystem. For support to be +// detected, the given Mount must be for a filesystem that supports fscrypt. +func isFsKeyringSupported(mount *filesystem.Mount) bool { + fsKeyringSupportedLock.Lock() + defer fsKeyringSupportedLock.Unlock() + if !fsKeyringSupportedKnown { + fsKeyringSupported = checkForFsKeyringSupport(mount) + fsKeyringSupportedKnown = true + } + return fsKeyringSupported +} + +// buildKeySpecifier converts the key descriptor string to an FscryptKeySpecifier. +func buildKeySpecifier(spec *unix.FscryptKeySpecifier, descriptor string) error { + descriptorBytes, err := hex.DecodeString(descriptor) + if err != nil { + return errors.Errorf("key descriptor %q is invalid", descriptor) + } + switch len(descriptorBytes) { + case unix.FSCRYPT_KEY_DESCRIPTOR_SIZE: + spec.Type = unix.FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR + default: + return errors.Errorf("key descriptor %q has unknown length", descriptor) + } + copy(spec.U[:], descriptorBytes) + return nil +} + +// fsAddEncryptionKey adds the specified encryption key to the specified filesystem. +func fsAddEncryptionKey(key *crypto.Key, descriptor string, + mount *filesystem.Mount, user *user.User) error { + + dir, err := os.Open(mount.Path) + if err != nil { + return err + } + defer dir.Close() + + argKey, err := crypto.NewBlankKey(int(unsafe.Sizeof(unix.FscryptAddKeyArg{})) + key.Len()) + if err != nil { + return err + } + defer argKey.Wipe() + arg := (*unix.FscryptAddKeyArg)(argKey.UnsafePtr()) + + if err = buildKeySpecifier(&arg.Key_spec, descriptor); err != nil { + return err + } + + raw := unsafe.Pointer(uintptr(argKey.UnsafePtr()) + unsafe.Sizeof(*arg)) + arg.Raw_size = uint32(key.Len()) + C.memcpy(raw, key.UnsafePtr(), C.size_t(key.Len())) + + _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(), + unix.FS_IOC_ADD_ENCRYPTION_KEY, uintptr(argKey.UnsafePtr())) + log.Printf("FS_IOC_ADD_ENCRYPTION_KEY(%q, %s, ) = %v", mount.Path, descriptor, errno) + if errno != 0 { + return errors.Wrap(ErrKeyAdd, errno.Error()) + } + return nil +} + +// fsRemoveEncryptionKey removes the specified encryption key from the specified +// filesystem. +func fsRemoveEncryptionKey(descriptor string, mount *filesystem.Mount, + user *user.User) error { + + dir, err := os.Open(mount.Path) + if err != nil { + return err + } + defer dir.Close() + + var arg unix.FscryptRemoveKeyArg + if err = buildKeySpecifier(&arg.Key_spec, descriptor); err != nil { + return err + } + + _, _, errno := unix.Syscall(unix.SYS_IOCTL, dir.Fd(), + unix.FS_IOC_REMOVE_ENCRYPTION_KEY, uintptr(unsafe.Pointer(&arg))) + 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 { + return ErrKeyFilesOpen + } + return nil + case unix.ENOKEY: + return ErrKeyNotPresent + default: + return errors.Wrap(ErrKeyRemove, errno.Error()) + } +} + +// fsGetEncryptionKeyStatus gets the status of the specified encryption key on +// the specified filesystem. +func fsGetEncryptionKeyStatus(descriptor string, mount *filesystem.Mount, + user *user.User) (KeyStatus, error) { + + dir, err := os.Open(mount.Path) + if err != nil { + return KeyStatusUnknown, err + } + defer dir.Close() + + var arg unix.FscryptGetKeyStatusArg + err = buildKeySpecifier(&arg.Key_spec, descriptor) + 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))) + 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 { + return KeyStatusUnknown, errors.Wrap(ErrKeySearch, errno.Error()) + } + switch arg.Status { + case unix.FSCRYPT_KEY_STATUS_ABSENT: + return KeyAbsent, nil + case unix.FSCRYPT_KEY_STATUS_PRESENT: + return KeyPresent, nil + case unix.FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED: + return KeyAbsentButFilesBusy, nil + default: + return KeyStatusUnknown, + errors.Wrapf(ErrKeySearch, "unknown key status (%d)", arg.Status) + } +} -- cgit v1.2.3 From 42e0dfe85ec7a75a2fa30c417d57eae60b5a881d Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: 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. --- keyring/fs_keyring.go | 100 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) (limited to 'keyring/fs_keyring.go') 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, ) = %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 -- cgit v1.2.3 From 068879664efd8a0f983cbc3e8115571047fe9edd Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: 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. --- keyring/fs_keyring.go | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) (limited to 'keyring/fs_keyring.go') 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: -- cgit v1.2.3