From f3dc4203ddb18b41a9ed137af02519f85180a1f7 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 30 Sep 2019 13:05:58 -0700 Subject: [PATCH] vold: use new ioctls to add/remove fscrypt keys when supported When the kernel supports the new fscrypt key management ioctls, use them instead of add_key() and keyctl_unlink(). This will be needed in order to support v2 encryption policies, since v2 encryption policies only support the new ioctls. The new ioctls have other advantages too. For example, FS_IOC_REMOVE_ENCRYPTION_KEY automatically evicts exactly the necessary kernel objects, so the drop_caches sysctl is no longer needed. This makes evicting keys faster and more reliable. FS_IOC_REMOVE_ENCRYPTION_KEY also detects if any files are still open and therefore couldn't be "locked", whereas this went undetected before. Therefore, to start out this patch adds support for using the new ioctls for v1 encryption policies, i.e. on existing devices. (Originally based on a patch by Satya Tangirala ) Bug: 140500828 Test: tested that a device using v1 policies continues to work, both with and without an updated kernel. See If64028d8580584b2c33c614cabd5d6b93657f608 for more details. Also checked via the log that the filesystem-level keyring is in fact used when supported. Change-Id: I296ef78138578a3fd773797ac0cd46af1296b959 --- FsCrypt.cpp | 44 +++++++++----- KeyUtil.cpp | 151 ++++++++++++++++++++++++++++++++++++++++++++----- KeyUtil.h | 9 ++- fscrypt_uapi.h | 48 ++++++++++++++++ 4 files changed, 221 insertions(+), 31 deletions(-) create mode 100644 fscrypt_uapi.h diff --git a/FsCrypt.cpp b/FsCrypt.cpp index 8d78473..e28002f 100644 --- a/FsCrypt.cpp +++ b/FsCrypt.cpp @@ -199,13 +199,23 @@ static bool read_and_fixate_user_ce_key(userid_t user_id, return false; } +// Install a key for use by encrypted files on the /data filesystem. +static bool install_data_key(const KeyBuffer& key, std::string* raw_ref) { + return android::vold::installKey(key, DATA_MNT_POINT, raw_ref); +} + +// Evict a key for use by encrypted files on the /data filesystem. +static bool evict_data_key(const std::string& raw_ref) { + return android::vold::evictKey(DATA_MNT_POINT, raw_ref); +} + static bool read_and_install_user_ce_key(userid_t user_id, const android::vold::KeyAuthentication& auth) { if (s_ce_key_raw_refs.count(user_id) != 0) return true; KeyBuffer ce_key; if (!read_and_fixate_user_ce_key(user_id, auth, &ce_key)) return false; std::string ce_raw_ref; - if (!android::vold::installKey(ce_key, &ce_raw_ref)) return false; + if (!install_data_key(ce_key, &ce_raw_ref)) return false; s_ce_keys[user_id] = std::move(ce_key); s_ce_key_raw_refs[user_id] = ce_raw_ref; LOG(DEBUG) << "Installed ce key for user " << user_id; @@ -255,10 +265,10 @@ static bool create_and_install_user_keys(userid_t user_id, bool create_ephemeral return false; } std::string de_raw_ref; - if (!android::vold::installKey(de_key, &de_raw_ref)) return false; + if (!install_data_key(de_key, &de_raw_ref)) return false; s_de_key_raw_refs[user_id] = de_raw_ref; std::string ce_raw_ref; - if (!android::vold::installKey(ce_key, &ce_raw_ref)) return false; + if (!install_data_key(ce_key, &ce_raw_ref)) return false; s_ce_keys[user_id] = ce_key; s_ce_key_raw_refs[user_id] = ce_raw_ref; LOG(DEBUG) << "Created keys for user " << user_id; @@ -325,7 +335,7 @@ static bool load_all_de_keys() { KeyBuffer key; if (!android::vold::retrieveKey(key_path, kEmptyAuthentication, &key)) return false; std::string raw_ref; - if (!android::vold::installKey(key, &raw_ref)) return false; + if (!install_data_key(key, &raw_ref)) return false; s_de_key_raw_refs[user_id] = raw_ref; LOG(DEBUG) << "Installed de key for user " << user_id; } @@ -345,7 +355,7 @@ bool fscrypt_initialize_systemwide_keys() { PolicyKeyRef device_ref; if (!android::vold::retrieveAndInstallKey(true, kEmptyAuthentication, device_key_path, - device_key_temp, &device_ref.key_raw_ref)) + device_key_temp, "", &device_ref.key_raw_ref)) return false; get_data_file_encryption_modes(&device_ref); @@ -360,7 +370,7 @@ bool fscrypt_initialize_systemwide_keys() { KeyBuffer per_boot_key; if (!android::vold::randomKey(&per_boot_key)) return false; std::string per_boot_raw_ref; - if (!android::vold::installKey(per_boot_key, &per_boot_raw_ref)) return false; + if (!install_data_key(per_boot_key, &per_boot_raw_ref)) return false; std::string per_boot_ref_filename = std::string("/data") + fscrypt_key_per_boot_ref; if (!android::vold::writeStringToFile(per_boot_raw_ref, per_boot_ref_filename)) return false; LOG(INFO) << "Wrote per boot key reference to:" << per_boot_ref_filename; @@ -419,15 +429,20 @@ bool fscrypt_vold_create_user_key(userid_t user_id, int serial, bool ephemeral) } // "Lock" all encrypted directories whose key has been removed. This is needed -// because merely removing the keyring key doesn't affect inodes in the kernel's -// inode cache whose per-file key was already set up. So to remove the per-file -// keys and make the files "appear encrypted", these inodes must be evicted. +// in the case where the keys are being put in the session keyring (rather in +// the newer filesystem-level keyrings), because removing a key from the session +// keyring doesn't affect inodes in the kernel's inode cache whose per-file key +// was already set up. So to remove the per-file keys and make the files +// "appear encrypted", these inodes must be evicted. // // To do this, sync() to clean all dirty inodes, then drop all reclaimable slab // objects systemwide. This is overkill, but it's the best available method // currently. Don't use drop_caches mode "3" because that also evicts pagecache // for in-use files; all files relevant here are already closed and sync'ed. -static void drop_caches() { +static void drop_caches_if_needed() { + if (android::vold::isFsKeyringSupported()) { + return; + } sync(); if (!writeStringToFile("2", "/proc/sys/vm/drop_caches")) { PLOG(ERROR) << "Failed to drop caches during key eviction"; @@ -440,8 +455,8 @@ static bool evict_ce_key(userid_t user_id) { std::string raw_ref; // If we haven't loaded the CE key, no need to evict it. if (lookup_key_ref(s_ce_key_raw_refs, user_id, &raw_ref)) { - success &= android::vold::evictKey(raw_ref); - drop_caches(); + success &= evict_data_key(raw_ref); + drop_caches_if_needed(); } s_ce_key_raw_refs.erase(user_id); return success; @@ -455,8 +470,7 @@ bool fscrypt_destroy_user_key(userid_t user_id) { bool success = true; std::string raw_ref; success &= evict_ce_key(user_id); - success &= - lookup_key_ref(s_de_key_raw_refs, user_id, &raw_ref) && android::vold::evictKey(raw_ref); + success &= lookup_key_ref(s_de_key_raw_refs, user_id, &raw_ref) && evict_data_key(raw_ref); s_de_key_raw_refs.erase(user_id); auto it = s_ephemeral_users.find(user_id); if (it != s_ephemeral_users.end()) { @@ -546,7 +560,7 @@ static bool read_or_create_volkey(const std::string& misc_path, const std::strin return false; } android::vold::KeyAuthentication auth("", secdiscardable_hash); - if (!android::vold::retrieveAndInstallKey(true, auth, key_path, key_path + "_tmp", + if (!android::vold::retrieveAndInstallKey(true, auth, key_path, key_path + "_tmp", volume_uuid, &key_ref->key_raw_ref)) return false; key_ref->contents_mode = diff --git a/KeyUtil.cpp b/KeyUtil.cpp index 12cae9b..c1a82fb 100644 --- a/KeyUtil.cpp +++ b/KeyUtil.cpp @@ -16,12 +16,14 @@ #include "KeyUtil.h" -#include #include #include #include +#include +#include #include +#include #include #include @@ -29,6 +31,7 @@ #include "KeyStorage.h" #include "Utils.h" +#include "fscrypt_uapi.h" namespace android { namespace vold { @@ -45,6 +48,42 @@ bool randomKey(KeyBuffer* key) { return true; } +// Return true if the kernel supports the ioctls to add/remove fscrypt keys +// directly to/from the filesystem. +bool isFsKeyringSupported(void) { + static bool initialized = false; + static bool supported; + + if (!initialized) { + android::base::unique_fd fd(open("/data", O_RDONLY | O_DIRECTORY | O_CLOEXEC)); + + // FS_IOC_ADD_ENCRYPTION_KEY with a NULL argument will fail with ENOTTY + // if the ioctl isn't supported. Otherwise it will fail with another + // error code such as EFAULT. + errno = 0; + (void)ioctl(fd, FS_IOC_ADD_ENCRYPTION_KEY, NULL); + if (errno == ENOTTY) { + LOG(INFO) << "Kernel doesn't support FS_IOC_ADD_ENCRYPTION_KEY. Falling back to " + "session keyring"; + supported = false; + } else { + if (errno != EFAULT) { + PLOG(WARNING) << "Unexpected error from FS_IOC_ADD_ENCRYPTION_KEY"; + } + LOG(DEBUG) << "Detected support for FS_IOC_ADD_ENCRYPTION_KEY"; + supported = true; + } + // There's no need to check for FS_IOC_REMOVE_ENCRYPTION_KEY, since it's + // guaranteed to be available if FS_IOC_ADD_ENCRYPTION_KEY is. There's + // also no need to check for support on external volumes separately from + // /data, since either the kernel supports the ioctls on all + // fscrypt-capable filesystems or it doesn't. + + initialized = true; + } + return supported; +} + // Get raw keyref - used to make keyname and to pass to ioctl static std::string generateKeyRef(const uint8_t* key, int length) { SHA512_CTX c; @@ -78,16 +117,20 @@ static bool fillKey(const KeyBuffer& key, fscrypt_key* fs_key) { static char const* const NAME_PREFIXES[] = {"ext4", "f2fs", "fscrypt", nullptr}; -static std::string keyname(const std::string& prefix, const std::string& raw_ref) { +static std::string keyrefstring(const std::string& raw_ref) { std::ostringstream o; - o << prefix << ":"; for (unsigned char i : raw_ref) { o << std::hex << std::setw(2) << std::setfill('0') << (int)i; } return o.str(); } -// Get the keyring we store all keys in +static std::string buildLegacyKeyName(const std::string& prefix, const std::string& raw_ref) { + return prefix + ":" + keyrefstring(raw_ref); +} + +// Get the ID of the keyring we store all fscrypt keys in when the kernel is too +// old to support FS_IOC_ADD_ENCRYPTION_KEY and FS_IOC_REMOVE_ENCRYPTION_KEY. static bool fscryptKeyring(key_serial_t* device_keyring) { *device_keyring = keyctl_search(KEY_SPEC_SESSION_KEYRING, "keyring", "fscrypt", 0); if (*device_keyring == -1) { @@ -97,19 +140,17 @@ static bool fscryptKeyring(key_serial_t* device_keyring) { return true; } -// Install password into global keyring -// Return raw key reference for use in policy -bool installKey(const KeyBuffer& key, std::string* raw_ref) { +// Add an encryption key to the legacy global session keyring. +static bool installKeyLegacy(const KeyBuffer& key, const std::string& raw_ref) { // Place fscrypt_key into automatically zeroing buffer. KeyBuffer fsKeyBuffer(sizeof(fscrypt_key)); fscrypt_key& fs_key = *reinterpret_cast(fsKeyBuffer.data()); if (!fillKey(key, &fs_key)) return false; - *raw_ref = generateKeyRef(fs_key.raw, fs_key.size); key_serial_t device_keyring; if (!fscryptKeyring(&device_keyring)) return false; for (char const* const* name_prefix = NAME_PREFIXES; *name_prefix != nullptr; name_prefix++) { - auto ref = keyname(*name_prefix, *raw_ref); + auto ref = buildLegacyKeyName(*name_prefix, raw_ref); key_serial_t key_id = add_key("logon", ref.c_str(), (void*)&fs_key, sizeof(fs_key), device_keyring); if (key_id == -1) { @@ -122,12 +163,55 @@ bool installKey(const KeyBuffer& key, std::string* raw_ref) { return true; } -bool evictKey(const std::string& raw_ref) { +// Install a file-based encryption key to the kernel, for use by encrypted files +// on the specified filesystem. +// +// We use FS_IOC_ADD_ENCRYPTION_KEY if the kernel supports it. Otherwise we add +// the key to the legacy global session keyring. +// +// Returns %true on success, %false on failure. On success also sets *raw_ref +// to the raw key reference for use in the encryption policy. +bool installKey(const KeyBuffer& key, const std::string& mountpoint, std::string* raw_ref) { + *raw_ref = generateKeyRef((const uint8_t*)key.data(), key.size()); + if (!isFsKeyringSupported()) { + return installKeyLegacy(key, *raw_ref); + } + + // Put the fscrypt_add_key_arg in an automatically-zeroing buffer, since we + // have to copy the raw key into it. + KeyBuffer arg_buf(sizeof(struct fscrypt_add_key_arg) + key.size(), 0); + struct fscrypt_add_key_arg* arg = (struct fscrypt_add_key_arg*)arg_buf.data(); + + arg->key_spec.type = FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR; + memcpy(arg->key_spec.u.descriptor, raw_ref.c_str(), FSCRYPT_KEY_DESCRIPTOR_SIZE); + + arg->raw_size = key.size(); + memcpy(arg->raw, key.data(), key.size()); + + android::base::unique_fd fd(open(mountpoint.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC)); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << mountpoint << " to install key"; + return false; + } + + if (ioctl(fd, FS_IOC_ADD_ENCRYPTION_KEY, arg) != 0) { + PLOG(ERROR) << "Failed to install fscrypt key with ref " << keyrefstring(*raw_ref) << " to " + << mountpoint; + return false; + } + + LOG(DEBUG) << "Installed fscrypt key with ref " << keyrefstring(*raw_ref) << " to " + << mountpoint; + return true; +} + +// Remove an encryption key from the legacy global session keyring. +static bool evictKeyLegacy(const std::string& raw_ref) { key_serial_t device_keyring; if (!fscryptKeyring(&device_keyring)) return false; bool success = true; for (char const* const* name_prefix = NAME_PREFIXES; *name_prefix != nullptr; name_prefix++) { - auto ref = keyname(*name_prefix, raw_ref); + auto ref = buildLegacyKeyName(*name_prefix, raw_ref); auto key_serial = keyctl_search(device_keyring, "logon", ref.c_str(), 0); // Unlink the key from the keyring. Prefer unlinking to revoking or @@ -144,9 +228,50 @@ bool evictKey(const std::string& raw_ref) { return success; } +// Evict a file-based encryption key from the kernel. +// +// We use FS_IOC_REMOVE_ENCRYPTION_KEY if the kernel supports it. Otherwise we +// remove the key from the legacy global session keyring. +// +// In the latter case, the caller is responsible for dropping caches. +bool evictKey(const std::string& mountpoint, const std::string& raw_ref) { + if (!isFsKeyringSupported()) { + return evictKeyLegacy(raw_ref); + } + + android::base::unique_fd fd(open(mountpoint.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC)); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << mountpoint << " to evict key"; + return false; + } + + struct fscrypt_remove_key_arg arg; + memset(&arg, 0, sizeof(arg)); + + arg.key_spec.type = FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR; + memcpy(arg.key_spec.u.descriptor, raw_ref.c_str(), FSCRYPT_KEY_DESCRIPTOR_SIZE); + + std::string ref = keyrefstring(raw_ref); + + if (ioctl(fd, FS_IOC_REMOVE_ENCRYPTION_KEY, &arg) != 0) { + PLOG(ERROR) << "Failed to evict fscrypt key with ref " << ref << " from " << mountpoint; + return false; + } + + LOG(DEBUG) << "Evicted fscrypt key with ref " << ref << " from " << mountpoint; + if (arg.removal_status_flags & FSCRYPT_KEY_REMOVAL_STATUS_FLAG_OTHER_USERS) { + // Should never happen because keys are only added/removed as root. + LOG(ERROR) << "Unexpected case: key with ref " << ref << " is still added by other users!"; + } else if (arg.removal_status_flags & FSCRYPT_KEY_REMOVAL_STATUS_FLAG_FILES_BUSY) { + LOG(ERROR) << "Files still open after removing key with ref " << ref + << ". These files were not locked!"; + } + return true; +} + bool retrieveAndInstallKey(bool create_if_absent, const KeyAuthentication& key_authentication, const std::string& key_path, const std::string& tmp_path, - std::string* key_ref) { + const std::string& volume_uuid, std::string* key_ref) { KeyBuffer key; if (pathExists(key_path)) { LOG(DEBUG) << "Key exists, using: " << key_path; @@ -161,7 +286,7 @@ bool retrieveAndInstallKey(bool create_if_absent, const KeyAuthentication& key_a if (!storeKeyAtomically(key_path, tmp_path, key_authentication, key)) return false; } - if (!installKey(key, key_ref)) { + if (!installKey(key, BuildDataPath(volume_uuid), key_ref)) { LOG(ERROR) << "Failed to install key in " << key_path; return false; } diff --git a/KeyUtil.h b/KeyUtil.h index 7ee6725..146f4d3 100644 --- a/KeyUtil.h +++ b/KeyUtil.h @@ -27,11 +27,14 @@ namespace android { namespace vold { bool randomKey(KeyBuffer* key); -bool installKey(const KeyBuffer& key, std::string* raw_ref); -bool evictKey(const std::string& raw_ref); + +bool isFsKeyringSupported(void); + +bool installKey(const KeyBuffer& key, const std::string& mountpoint, std::string* raw_ref); +bool evictKey(const std::string& mountpoint, const std::string& raw_ref); bool retrieveAndInstallKey(bool create_if_absent, const KeyAuthentication& key_authentication, const std::string& key_path, const std::string& tmp_path, - std::string* key_ref); + const std::string& volume_uuid, std::string* key_ref); bool retrieveKey(bool create_if_absent, const std::string& key_path, const std::string& tmp_path, KeyBuffer* key, bool keepOld = true); diff --git a/fscrypt_uapi.h b/fscrypt_uapi.h new file mode 100644 index 0000000..3999036 --- /dev/null +++ b/fscrypt_uapi.h @@ -0,0 +1,48 @@ +#ifndef _UAPI_LINUX_FSCRYPT_H +#define _UAPI_LINUX_FSCRYPT_H + +// Definitions for FS_IOC_ADD_ENCRYPTION_KEY and FS_IOC_REMOVE_ENCRYPTION_KEY + +// TODO: switch to once it's in Bionic + +#ifndef FS_IOC_ADD_ENCRYPTION_KEY + +#include + +#define FSCRYPT_KEY_DESCRIPTOR_SIZE 8 +#define FSCRYPT_KEY_IDENTIFIER_SIZE 16 + +#define FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR 1 +#define FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER 2 + +struct fscrypt_key_specifier { + __u32 type; + __u32 __reserved; + union { + __u8 __reserved[32]; + __u8 descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE]; + __u8 identifier[FSCRYPT_KEY_IDENTIFIER_SIZE]; + } u; +}; + +struct fscrypt_add_key_arg { + struct fscrypt_key_specifier key_spec; + __u32 raw_size; + __u32 __reserved[9]; + __u8 raw[]; +}; + +struct fscrypt_remove_key_arg { + struct fscrypt_key_specifier key_spec; +#define FSCRYPT_KEY_REMOVAL_STATUS_FLAG_FILES_BUSY 0x00000001 +#define FSCRYPT_KEY_REMOVAL_STATUS_FLAG_OTHER_USERS 0x00000002 + __u32 removal_status_flags; + __u32 __reserved[5]; +}; + +#define FS_IOC_ADD_ENCRYPTION_KEY _IOWR('f', 23, struct fscrypt_add_key_arg) +#define FS_IOC_REMOVE_ENCRYPTION_KEY _IOWR('f', 24, struct fscrypt_remove_key_arg) + +#endif /* FS_IOC_ADD_ENCRYPTION_KEY */ + +#endif /* _UAPI_LINUX_FSCRYPT_H */