From 9eb2976da7960aeb16015c437befe228b8c39f2f Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 23 Oct 2020 17:51:31 -0700 Subject: [PATCH 01/20] fscrypt: remove kernel-internal constants from UAPI header There isn't really any valid reason to use __FSCRYPT_MODE_MAX or FSCRYPT_POLICY_FLAGS_VALID in a userspace program. These constants are only meant to be used by the kernel internally, and they are defined in the UAPI header next to the mode numbers and flags only so that kernel developers don't forget to update them when adding new modes or flags. In https://lkml.kernel.org/r/20201005074133.1958633-2-satyat@google.com there was an example of someone wanting to use __FSCRYPT_MODE_MAX in a user program, and it was wrong because the program would have broken if __FSCRYPT_MODE_MAX were ever increased. So having this definition available is harmful. FSCRYPT_POLICY_FLAGS_VALID has the same problem. So, remove these definitions from the UAPI header. Replace FSCRYPT_POLICY_FLAGS_VALID with just listing the valid flags explicitly in the one kernel function that needs it. Move __FSCRYPT_MODE_MAX to fscrypt_private.h, remove the double underscores (which were only present to discourage use by userspace), and add a BUILD_BUG_ON() and comments to (hopefully) ensure it is kept in sync. Keep the old name FS_POLICY_FLAGS_VALID, since it's been around for longer and there's a greater chance that removing it would break source compatibility with some program. Indeed, mtd-utils is using it in an #ifdef, and removing it would introduce compiler warnings (about FS_POLICY_FLAGS_PAD_* being redefined) into the mtd-utils build. However, reduce its value to 0x07 so that it only includes the flags with old names (the ones present before Linux 5.4), and try to make it clear that it's now "frozen" and no new flags should be added to it. Fixes: 2336d0deb2d4 ("fscrypt: use FSCRYPT_ prefix for uapi constants") Cc: # v5.4+ Link: https://lore.kernel.org/r/20201024005132.495952-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/fscrypt_private.h | 9 ++++++--- fs/crypto/keyring.c | 2 +- fs/crypto/keysetup.c | 4 +++- fs/crypto/policy.c | 5 ++++- include/uapi/linux/fscrypt.h | 5 ++--- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h index df9c48c1fbf7..2c6526b60d9c 100644 --- a/fs/crypto/fscrypt_private.h +++ b/fs/crypto/fscrypt_private.h @@ -25,6 +25,9 @@ #define FSCRYPT_CONTEXT_V1 1 #define FSCRYPT_CONTEXT_V2 2 +/* Keep this in sync with include/uapi/linux/fscrypt.h */ +#define FSCRYPT_MODE_MAX FSCRYPT_MODE_ADIANTUM + struct fscrypt_context_v1 { u8 version; /* FSCRYPT_CONTEXT_V1 */ u8 contents_encryption_mode; @@ -490,9 +493,9 @@ struct fscrypt_master_key { * Per-mode encryption keys for the various types of encryption policies * that use them. Allocated and derived on-demand. */ - struct fscrypt_prepared_key mk_direct_keys[__FSCRYPT_MODE_MAX + 1]; - struct fscrypt_prepared_key mk_iv_ino_lblk_64_keys[__FSCRYPT_MODE_MAX + 1]; - struct fscrypt_prepared_key mk_iv_ino_lblk_32_keys[__FSCRYPT_MODE_MAX + 1]; + struct fscrypt_prepared_key mk_direct_keys[FSCRYPT_MODE_MAX + 1]; + struct fscrypt_prepared_key mk_iv_ino_lblk_64_keys[FSCRYPT_MODE_MAX + 1]; + struct fscrypt_prepared_key mk_iv_ino_lblk_32_keys[FSCRYPT_MODE_MAX + 1]; /* Hash key for inode numbers. Initialized only when needed. */ siphash_key_t mk_ino_hash_key; diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c index 53cc552a7b8f..d7ec52cb3d9a 100644 --- a/fs/crypto/keyring.c +++ b/fs/crypto/keyring.c @@ -44,7 +44,7 @@ static void free_master_key(struct fscrypt_master_key *mk) wipe_master_key_secret(&mk->mk_secret); - for (i = 0; i <= __FSCRYPT_MODE_MAX; i++) { + for (i = 0; i <= FSCRYPT_MODE_MAX; i++) { fscrypt_destroy_prepared_key(&mk->mk_direct_keys[i]); fscrypt_destroy_prepared_key(&mk->mk_iv_ino_lblk_64_keys[i]); fscrypt_destroy_prepared_key(&mk->mk_iv_ino_lblk_32_keys[i]); diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c index d595abb8ef90..31fb08d94f87 100644 --- a/fs/crypto/keysetup.c +++ b/fs/crypto/keysetup.c @@ -56,6 +56,8 @@ static struct fscrypt_mode * select_encryption_mode(const union fscrypt_policy *policy, const struct inode *inode) { + BUILD_BUG_ON(ARRAY_SIZE(fscrypt_modes) != FSCRYPT_MODE_MAX + 1); + if (S_ISREG(inode->i_mode)) return &fscrypt_modes[fscrypt_policy_contents_mode(policy)]; @@ -168,7 +170,7 @@ static int setup_per_mode_enc_key(struct fscrypt_info *ci, unsigned int hkdf_infolen = 0; int err; - if (WARN_ON(mode_num > __FSCRYPT_MODE_MAX)) + if (WARN_ON(mode_num > FSCRYPT_MODE_MAX)) return -EINVAL; prep_key = &keys[mode_num]; diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c index 4441d9944b9e..faa0f21daa68 100644 --- a/fs/crypto/policy.c +++ b/fs/crypto/policy.c @@ -175,7 +175,10 @@ static bool fscrypt_supported_v2_policy(const struct fscrypt_policy_v2 *policy, return false; } - if (policy->flags & ~FSCRYPT_POLICY_FLAGS_VALID) { + if (policy->flags & ~(FSCRYPT_POLICY_FLAGS_PAD_MASK | + FSCRYPT_POLICY_FLAG_DIRECT_KEY | + FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 | + FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32)) { fscrypt_warn(inode, "Unsupported encryption flags (0x%02x)", policy->flags); return false; diff --git a/include/uapi/linux/fscrypt.h b/include/uapi/linux/fscrypt.h index e5de60336938..9f4428be3e36 100644 --- a/include/uapi/linux/fscrypt.h +++ b/include/uapi/linux/fscrypt.h @@ -20,7 +20,6 @@ #define FSCRYPT_POLICY_FLAG_DIRECT_KEY 0x04 #define FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 0x08 #define FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 0x10 -#define FSCRYPT_POLICY_FLAGS_VALID 0x1F /* Encryption algorithms */ #define FSCRYPT_MODE_AES_256_XTS 1 @@ -28,7 +27,7 @@ #define FSCRYPT_MODE_AES_128_CBC 5 #define FSCRYPT_MODE_AES_128_CTS 6 #define FSCRYPT_MODE_ADIANTUM 9 -#define __FSCRYPT_MODE_MAX 9 +/* If adding a mode number > 9, update FSCRYPT_MODE_MAX in fscrypt_private.h */ /* * Legacy policy version; ad-hoc KDF and no key verification. @@ -177,7 +176,7 @@ struct fscrypt_get_key_status_arg { #define FS_POLICY_FLAGS_PAD_32 FSCRYPT_POLICY_FLAGS_PAD_32 #define FS_POLICY_FLAGS_PAD_MASK FSCRYPT_POLICY_FLAGS_PAD_MASK #define FS_POLICY_FLAG_DIRECT_KEY FSCRYPT_POLICY_FLAG_DIRECT_KEY -#define FS_POLICY_FLAGS_VALID FSCRYPT_POLICY_FLAGS_VALID +#define FS_POLICY_FLAGS_VALID 0x07 /* contains old flags only */ #define FS_ENCRYPTION_MODE_INVALID 0 /* never used */ #define FS_ENCRYPTION_MODE_AES_256_XTS FSCRYPT_MODE_AES_256_XTS #define FS_ENCRYPTION_MODE_AES_256_GCM 2 /* never used */ From 6b764a0e308d7bfd470fc1d18b66ecca586d3a23 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 17 Nov 2020 23:56:05 -0800 Subject: [PATCH 02/20] fscrypt: add fscrypt_is_nokey_name() It's possible to create a duplicate filename in an encrypted directory by creating a file concurrently with adding the encryption key. Specifically, sys_open(O_CREAT) (or sys_mkdir(), sys_mknod(), or sys_symlink()) can lookup the target filename while the directory's encryption key hasn't been added yet, resulting in a negative no-key dentry. The VFS then calls ->create() (or ->mkdir(), ->mknod(), or ->symlink()) because the dentry is negative. Normally, ->create() would return -ENOKEY due to the directory's key being unavailable. However, if the key was added between the dentry lookup and ->create(), then the filesystem will go ahead and try to create the file. If the target filename happens to already exist as a normal name (not a no-key name), a duplicate filename may be added to the directory. In order to fix this, we need to fix the filesystems to prevent ->create(), ->mkdir(), ->mknod(), and ->symlink() on no-key names. (->rename() and ->link() need it too, but those are already handled correctly by fscrypt_prepare_rename() and fscrypt_prepare_link().) In preparation for this, add a helper function fscrypt_is_nokey_name() that filesystems can use to do this check. Use this helper function for the existing checks that fs/crypto/ does for rename and link. Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20201118075609.120337-2-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/hooks.c | 5 +++-- include/linux/fscrypt.h | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c index 9006fa983335..63a7c845f74a 100644 --- a/fs/crypto/hooks.c +++ b/fs/crypto/hooks.c @@ -61,7 +61,7 @@ int __fscrypt_prepare_link(struct inode *inode, struct inode *dir, return err; /* ... in case we looked up no-key name before key was added */ - if (dentry->d_flags & DCACHE_NOKEY_NAME) + if (fscrypt_is_nokey_name(dentry)) return -ENOKEY; if (!fscrypt_has_permitted_context(dir, inode)) @@ -86,7 +86,8 @@ int __fscrypt_prepare_rename(struct inode *old_dir, struct dentry *old_dentry, return err; /* ... in case we looked up no-key name(s) before key was added */ - if ((old_dentry->d_flags | new_dentry->d_flags) & DCACHE_NOKEY_NAME) + if (fscrypt_is_nokey_name(old_dentry) || + fscrypt_is_nokey_name(new_dentry)) return -ENOKEY; if (old_dir != new_dir) { diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index e72f80482671..c8159e92fd1a 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -111,6 +111,35 @@ static inline void fscrypt_handle_d_move(struct dentry *dentry) dentry->d_flags &= ~DCACHE_NOKEY_NAME; } +/** + * fscrypt_is_nokey_name() - test whether a dentry is a no-key name + * @dentry: the dentry to check + * + * This returns true if the dentry is a no-key dentry. A no-key dentry is a + * dentry that was created in an encrypted directory that hasn't had its + * encryption key added yet. Such dentries may be either positive or negative. + * + * When a filesystem is asked to create a new filename in an encrypted directory + * and the new filename's dentry is a no-key dentry, it must fail the operation + * with ENOKEY. This includes ->create(), ->mkdir(), ->mknod(), ->symlink(), + * ->rename(), and ->link(). (However, ->rename() and ->link() are already + * handled by fscrypt_prepare_rename() and fscrypt_prepare_link().) + * + * This is necessary because creating a filename requires the directory's + * encryption key, but just checking for the key on the directory inode during + * the final filesystem operation doesn't guarantee that the key was available + * during the preceding dentry lookup. And the key must have already been + * available during the dentry lookup in order for it to have been checked + * whether the filename already exists in the directory and for the new file's + * dentry not to be invalidated due to it incorrectly having the no-key flag. + * + * Return: %true if the dentry is a no-key name + */ +static inline bool fscrypt_is_nokey_name(const struct dentry *dentry) +{ + return dentry->d_flags & DCACHE_NOKEY_NAME; +} + /* crypto.c */ void fscrypt_enqueue_decrypt_work(struct work_struct *); @@ -244,6 +273,11 @@ static inline void fscrypt_handle_d_move(struct dentry *dentry) { } +static inline bool fscrypt_is_nokey_name(const struct dentry *dentry) +{ + return false; +} + /* crypto.c */ static inline void fscrypt_enqueue_decrypt_work(struct work_struct *work) { From e09b02e8c65f64f1a35d1cb30b3b28a55d52386f Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 17 Nov 2020 23:56:06 -0800 Subject: [PATCH 03/20] ext4: prevent creating duplicate encrypted filenames As described in "fscrypt: add fscrypt_is_nokey_name()", it's possible to create a duplicate filename in an encrypted directory by creating a file concurrently with adding the directory's encryption key. Fix this bug on ext4 by rejecting no-key dentries in ext4_add_entry(). Note that the duplicate check in ext4_find_dest_de() sometimes prevented this bug. However in many cases it didn't, since ext4_find_dest_de() doesn't examine every dentry. Fixes: 4461471107b7 ("ext4 crypto: enable filename encryption") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20201118075609.120337-3-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/ext4/namei.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 12a417ff5648..1e75d88e5d82 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -2196,6 +2196,9 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry, if (!dentry->d_name.len) return -EINVAL; + if (fscrypt_is_nokey_name(dentry)) + return -ENOKEY; + #ifdef CONFIG_UNICODE if (sb_has_strict_encoding(sb) && IS_CASEFOLDED(dir) && sb->s_encoding && utf8_validate(sb->s_encoding, &dentry->d_name)) From ee957e5d924c89784e559ee97f697d8dee876b41 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 17 Nov 2020 23:56:07 -0800 Subject: [PATCH 04/20] f2fs: prevent creating duplicate encrypted filenames As described in "fscrypt: add fscrypt_is_nokey_name()", it's possible to create a duplicate filename in an encrypted directory by creating a file concurrently with adding the directory's encryption key. Fix this bug on f2fs by rejecting no-key dentries in f2fs_add_link(). Note that the weird check for the current task in f2fs_do_add_link() seems to make this bug difficult to reproduce on f2fs. Fixes: 9ea97163c6da ("f2fs crypto: add filename encryption for f2fs_add_link") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20201118075609.120337-4-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/f2fs/f2fs.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 7364d453783f..bb11759191dc 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -3219,6 +3219,8 @@ bool f2fs_empty_dir(struct inode *dir); static inline int f2fs_add_link(struct dentry *dentry, struct inode *inode) { + if (fscrypt_is_nokey_name(dentry)) + return -ENOKEY; return f2fs_do_add_link(d_inode(dentry->d_parent), &dentry->d_name, inode, inode->i_ino, inode->i_mode); } From cfdfa25f083f44419d080f9fbdd9bedb66aabe2b Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 17 Nov 2020 23:56:08 -0800 Subject: [PATCH 05/20] ubifs: prevent creating duplicate encrypted filenames As described in "fscrypt: add fscrypt_is_nokey_name()", it's possible to create a duplicate filename in an encrypted directory by creating a file concurrently with adding the directory's encryption key. Fix this bug on ubifs by rejecting no-key dentries in ubifs_create(), ubifs_mkdir(), ubifs_mknod(), and ubifs_symlink(). Note that ubifs doesn't actually report the duplicate filenames from readdir, but rather it seems to replace the original dentry with a new one (which is still wrong, just a different effect from ext4). On ubifs, this fixes xfstest generic/595 as well as the new xfstest I wrote specifically for this bug. Fixes: f4f61d2cc6d8 ("ubifs: Implement encrypted filenames") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20201118075609.120337-5-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/ubifs/dir.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index 7a920434d741..0379a785b7f6 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -271,6 +271,15 @@ static struct dentry *ubifs_lookup(struct inode *dir, struct dentry *dentry, return d_splice_alias(inode, dentry); } +static int ubifs_prepare_create(struct inode *dir, struct dentry *dentry, + struct fscrypt_name *nm) +{ + if (fscrypt_is_nokey_name(dentry)) + return -ENOKEY; + + return fscrypt_setup_filename(dir, &dentry->d_name, 0, nm); +} + static int ubifs_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) { @@ -294,7 +303,7 @@ static int ubifs_create(struct inode *dir, struct dentry *dentry, umode_t mode, if (err) return err; - err = fscrypt_setup_filename(dir, &dentry->d_name, 0, &nm); + err = ubifs_prepare_create(dir, dentry, &nm); if (err) goto out_budg; @@ -954,7 +963,7 @@ static int ubifs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) if (err) return err; - err = fscrypt_setup_filename(dir, &dentry->d_name, 0, &nm); + err = ubifs_prepare_create(dir, dentry, &nm); if (err) goto out_budg; @@ -1039,7 +1048,7 @@ static int ubifs_mknod(struct inode *dir, struct dentry *dentry, return err; } - err = fscrypt_setup_filename(dir, &dentry->d_name, 0, &nm); + err = ubifs_prepare_create(dir, dentry, &nm); if (err) { kfree(dev); goto out_budg; @@ -1123,7 +1132,7 @@ static int ubifs_symlink(struct inode *dir, struct dentry *dentry, if (err) return err; - err = fscrypt_setup_filename(dir, &dentry->d_name, 0, &nm); + err = ubifs_prepare_create(dir, dentry, &nm); if (err) goto out_budg; From 26461e30a765c8f1ed1e99cd5f10a83ee4f47ccd Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 17 Nov 2020 23:56:09 -0800 Subject: [PATCH 06/20] fscrypt: remove unnecessary calls to fscrypt_require_key() In an encrypted directory, a regular dentry (one that doesn't have the no-key name flag) can only be created if the directory's encryption key is available. Therefore the calls to fscrypt_require_key() in __fscrypt_prepare_link() and __fscrypt_prepare_rename() are unnecessary, as these functions already check that the dentries they're given aren't no-key names. Remove these unnecessary calls to fscrypt_require_key(). Link: https://lore.kernel.org/r/20201118075609.120337-6-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/hooks.c | 26 ++++++++------------------ include/linux/fscrypt.h | 3 +-- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c index 63a7c845f74a..23156877a02f 100644 --- a/fs/crypto/hooks.c +++ b/fs/crypto/hooks.c @@ -54,15 +54,12 @@ EXPORT_SYMBOL_GPL(fscrypt_file_open); int __fscrypt_prepare_link(struct inode *inode, struct inode *dir, struct dentry *dentry) { - int err; - - err = fscrypt_require_key(dir); - if (err) - return err; - - /* ... in case we looked up no-key name before key was added */ if (fscrypt_is_nokey_name(dentry)) return -ENOKEY; + /* + * We don't need to separately check that the directory inode's key is + * available, as it's implied by the dentry not being a no-key name. + */ if (!fscrypt_has_permitted_context(dir, inode)) return -EXDEV; @@ -75,20 +72,13 @@ int __fscrypt_prepare_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { - int err; - - err = fscrypt_require_key(old_dir); - if (err) - return err; - - err = fscrypt_require_key(new_dir); - if (err) - return err; - - /* ... in case we looked up no-key name(s) before key was added */ if (fscrypt_is_nokey_name(old_dentry) || fscrypt_is_nokey_name(new_dentry)) return -ENOKEY; + /* + * We don't need to separately check that the directory inodes' keys are + * available, as it's implied by the dentries not being no-key names. + */ if (old_dir != new_dir) { if (IS_ENCRYPTED(new_dir) && diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index c8159e92fd1a..197811a1bbd3 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -710,8 +710,7 @@ static inline int fscrypt_require_key(struct inode *inode) * * A new link can only be added to an encrypted directory if the directory's * encryption key is available --- since otherwise we'd have no way to encrypt - * the filename. Therefore, we first set up the directory's encryption key (if - * not already done) and return an error if it's unavailable. + * the filename. * * We also verify that the link will not violate the constraint that all files * in an encrypted directory tree use the same encryption policy. From 3b743b00636648afb645abfae10bc0131bd793f8 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 16 Nov 2020 19:26:26 -0800 Subject: [PATCH 07/20] fscrypt: simplify master key locking The stated reasons for separating fscrypt_master_key::mk_secret_sem from the standard semaphore contained in every 'struct key' no longer apply. First, due to commit a992b20cd4ee ("fscrypt: add fscrypt_prepare_new_inode() and fscrypt_set_context()"), fscrypt_get_encryption_info() is no longer called from within a filesystem transaction. Second, due to commit d3ec10aa9581 ("KEYS: Don't write out to userspace while holding key semaphore"), the semaphore for the "keyring" key type no longer ranks above page faults. That leaves performance as the only possible reason to keep the separate mk_secret_sem. Specifically, having mk_secret_sem reduces the contention between setup_file_encryption_key() and FS_IOC_{ADD,REMOVE}_ENCRYPTION_KEY. However, these ioctls aren't executed often, so this doesn't seem to be worth the extra complexity. Therefore, simplify the locking design by just using key->sem instead of mk_secret_sem. Link: https://lore.kernel.org/r/20201117032626.320275-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/fscrypt_private.h | 19 ++++++------------- fs/crypto/hooks.c | 8 +++++--- fs/crypto/keyring.c | 8 +------- fs/crypto/keysetup.c | 20 +++++++++----------- 4 files changed, 21 insertions(+), 34 deletions(-) diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h index 2c6526b60d9c..9cb9674524af 100644 --- a/fs/crypto/fscrypt_private.h +++ b/fs/crypto/fscrypt_private.h @@ -438,16 +438,9 @@ struct fscrypt_master_key { * FS_IOC_REMOVE_ENCRYPTION_KEY can be retried, or * FS_IOC_ADD_ENCRYPTION_KEY can add the secret again. * - * Locking: protected by key->sem (outer) and mk_secret_sem (inner). - * The reason for two locks is that key->sem also protects modifying - * mk_users, which ranks it above the semaphore for the keyring key - * type, which is in turn above page faults (via keyring_read). But - * sometimes filesystems call fscrypt_get_encryption_info() from within - * a transaction, which ranks it below page faults. So we need a - * separate lock which protects mk_secret but not also mk_users. + * Locking: protected by this master key's key->sem. */ struct fscrypt_master_key_secret mk_secret; - struct rw_semaphore mk_secret_sem; /* * For v1 policy keys: an arbitrary key descriptor which was assigned by @@ -466,8 +459,8 @@ struct fscrypt_master_key { * * This is NULL for v1 policy keys; those can only be added by root. * - * Locking: in addition to this keyrings own semaphore, this is - * protected by the master key's key->sem, so we can do atomic + * Locking: in addition to this keyring's own semaphore, this is + * protected by this master key's key->sem, so we can do atomic * search+insert. It can also be searched without taking any locks, but * in that case the returned key may have already been removed. */ @@ -509,9 +502,9 @@ is_master_key_secret_present(const struct fscrypt_master_key_secret *secret) /* * The READ_ONCE() is only necessary for fscrypt_drop_inode() and * fscrypt_key_describe(). These run in atomic context, so they can't - * take ->mk_secret_sem and thus 'secret' can change concurrently which - * would be a data race. But they only need to know whether the secret - * *was* present at the time of check, so READ_ONCE() suffices. + * take the key semaphore and thus 'secret' can change concurrently + * which would be a data race. But they only need to know whether the + * secret *was* present at the time of check, so READ_ONCE() suffices. */ return READ_ONCE(secret->size) != 0; } diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c index 23156877a02f..ba1101bb46a7 100644 --- a/fs/crypto/hooks.c +++ b/fs/crypto/hooks.c @@ -128,6 +128,7 @@ int fscrypt_prepare_setflags(struct inode *inode, unsigned int oldflags, unsigned int flags) { struct fscrypt_info *ci; + struct key *key; struct fscrypt_master_key *mk; int err; @@ -143,13 +144,14 @@ int fscrypt_prepare_setflags(struct inode *inode, ci = inode->i_crypt_info; if (ci->ci_policy.version != FSCRYPT_POLICY_V2) return -EINVAL; - mk = ci->ci_master_key->payload.data[0]; - down_read(&mk->mk_secret_sem); + key = ci->ci_master_key; + mk = key->payload.data[0]; + down_read(&key->sem); if (is_master_key_secret_present(&mk->mk_secret)) err = fscrypt_derive_dirhash_key(ci, mk); else err = -ENOKEY; - up_read(&mk->mk_secret_sem); + up_read(&key->sem); return err; } return 0; diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c index d7ec52cb3d9a..0b3ffbb4faf4 100644 --- a/fs/crypto/keyring.c +++ b/fs/crypto/keyring.c @@ -347,7 +347,6 @@ static int add_new_master_key(struct fscrypt_master_key_secret *secret, mk->mk_spec = *mk_spec; move_master_key_secret(&mk->mk_secret, secret); - init_rwsem(&mk->mk_secret_sem); refcount_set(&mk->mk_refcount, 1); /* secret is present */ INIT_LIST_HEAD(&mk->mk_decrypted_inodes); @@ -427,11 +426,8 @@ static int add_existing_master_key(struct fscrypt_master_key *mk, } /* Re-add the secret if needed. */ - if (rekey) { - down_write(&mk->mk_secret_sem); + if (rekey) move_master_key_secret(&mk->mk_secret, secret); - up_write(&mk->mk_secret_sem); - } return 0; } @@ -975,10 +971,8 @@ static int do_remove_key(struct file *filp, void __user *_uarg, bool all_users) /* No user claims remaining. Go ahead and wipe the secret. */ dead = false; if (is_master_key_secret_present(&mk->mk_secret)) { - down_write(&mk->mk_secret_sem); wipe_master_key_secret(&mk->mk_secret); dead = refcount_dec_and_test(&mk->mk_refcount); - up_write(&mk->mk_secret_sem); } up_write(&key->sem); if (dead) { diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c index 31fb08d94f87..50675b42d5b7 100644 --- a/fs/crypto/keysetup.c +++ b/fs/crypto/keysetup.c @@ -337,11 +337,11 @@ static int fscrypt_setup_v2_file_key(struct fscrypt_info *ci, * Find the master key, then set up the inode's actual encryption key. * * If the master key is found in the filesystem-level keyring, then the - * corresponding 'struct key' is returned in *master_key_ret with - * ->mk_secret_sem read-locked. This is needed to ensure that only one task - * links the fscrypt_info into ->mk_decrypted_inodes (as multiple tasks may race - * to create an fscrypt_info for the same inode), and to synchronize the master - * key being removed with a new inode starting to use it. + * corresponding 'struct key' is returned in *master_key_ret with its semaphore + * read-locked. This is needed to ensure that only one task links the + * fscrypt_info into ->mk_decrypted_inodes (as multiple tasks may race to create + * an fscrypt_info for the same inode), and to synchronize the master key being + * removed with a new inode starting to use it. */ static int setup_file_encryption_key(struct fscrypt_info *ci, bool need_dirhash_key, @@ -390,7 +390,7 @@ static int setup_file_encryption_key(struct fscrypt_info *ci, } mk = key->payload.data[0]; - down_read(&mk->mk_secret_sem); + down_read(&key->sem); /* Has the secret been removed (via FS_IOC_REMOVE_ENCRYPTION_KEY)? */ if (!is_master_key_secret_present(&mk->mk_secret)) { @@ -433,7 +433,7 @@ static int setup_file_encryption_key(struct fscrypt_info *ci, return 0; out_release_key: - up_read(&mk->mk_secret_sem); + up_read(&key->sem); key_put(key); return err; } @@ -536,9 +536,7 @@ fscrypt_setup_encryption_info(struct inode *inode, res = 0; out: if (master_key) { - struct fscrypt_master_key *mk = master_key->payload.data[0]; - - up_read(&mk->mk_secret_sem); + up_read(&master_key->sem); key_put(master_key); } put_crypt_info(crypt_info); @@ -712,7 +710,7 @@ int fscrypt_drop_inode(struct inode *inode) return 0; /* - * Note: since we aren't holding ->mk_secret_sem, the result here can + * Note: since we aren't holding the key semaphore, the result here can * immediately become outdated. But there's no correctness problem with * unnecessarily evicting. Nor is there a correctness problem with not * evicting while iput() is racing with the key being removed, since From 757f7f130241b1c0696be02b33c5a96137a2032c Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 2 Dec 2020 18:20:33 -0800 Subject: [PATCH 08/20] ext4: remove ext4_dir_open() Since encrypted directories can be opened and searched without their key being available, and each readdir and ->lookup() tries to set up the key, trying to set up the key in ->open() too isn't really useful. Just remove it so that directories don't need an ->open() method anymore, and so that we eliminate a use of fscrypt_get_encryption_info() (which I'd like to stop exporting to filesystems). Reviewed-by: Andreas Dilger Link: https://lore.kernel.org/r/20201203022041.230976-2-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/ext4/dir.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c index e757319a4472..7e9ee191c445 100644 --- a/fs/ext4/dir.c +++ b/fs/ext4/dir.c @@ -616,13 +616,6 @@ static int ext4_dx_readdir(struct file *file, struct dir_context *ctx) return 0; } -static int ext4_dir_open(struct inode * inode, struct file * filp) -{ - if (IS_ENCRYPTED(inode)) - return fscrypt_get_encryption_info(inode) ? -EACCES : 0; - return 0; -} - static int ext4_release_dir(struct inode *inode, struct file *filp) { if (filp->private_data) @@ -664,6 +657,5 @@ const struct file_operations ext4_dir_operations = { .compat_ioctl = ext4_compat_ioctl, #endif .fsync = ext4_sync_file, - .open = ext4_dir_open, .release = ext4_release_dir, }; From 39eea388020dd9b23c8a3a18ca2e0658c67f1e73 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 2 Dec 2020 18:20:34 -0800 Subject: [PATCH 09/20] f2fs: remove f2fs_dir_open() Since encrypted directories can be opened and searched without their key being available, and each readdir and ->lookup() tries to set up the key, trying to set up the key in ->open() too isn't really useful. Just remove it so that directories don't need an ->open() method anymore, and so that we eliminate a use of fscrypt_get_encryption_info() (which I'd like to stop exporting to filesystems). Reviewed-by: Chao Yu Link: https://lore.kernel.org/r/20201203022041.230976-3-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/f2fs/dir.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c index 82b58d1f80eb..caf203cd61bd 100644 --- a/fs/f2fs/dir.c +++ b/fs/f2fs/dir.c @@ -1135,19 +1135,11 @@ static int f2fs_readdir(struct file *file, struct dir_context *ctx) return err < 0 ? err : 0; } -static int f2fs_dir_open(struct inode *inode, struct file *filp) -{ - if (IS_ENCRYPTED(inode)) - return fscrypt_get_encryption_info(inode) ? -EACCES : 0; - return 0; -} - const struct file_operations f2fs_dir_operations = { .llseek = generic_file_llseek, .read = generic_read_dir, .iterate_shared = f2fs_readdir, .fsync = f2fs_sync_file, - .open = f2fs_dir_open, .unlocked_ioctl = f2fs_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = f2fs_compat_ioctl, From c247d00ee005bbfdd518ed052f1f460ff1995beb Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 2 Dec 2020 18:20:35 -0800 Subject: [PATCH 10/20] ubifs: remove ubifs_dir_open() Since encrypted directories can be opened and searched without their key being available, and each readdir and ->lookup() tries to set up the key, trying to set up the key in ->open() too isn't really useful. Just remove it so that directories don't need an ->open() method anymore, and so that we eliminate a use of fscrypt_get_encryption_info() (which I'd like to stop exporting to filesystems). Link: https://lore.kernel.org/r/20201203022041.230976-4-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/ubifs/dir.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index 0379a785b7f6..a9a2e7b6a036 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -1620,14 +1620,6 @@ int ubifs_getattr(const struct path *path, struct kstat *stat, return 0; } -static int ubifs_dir_open(struct inode *dir, struct file *file) -{ - if (IS_ENCRYPTED(dir)) - return fscrypt_get_encryption_info(dir) ? -EACCES : 0; - - return 0; -} - const struct inode_operations ubifs_dir_inode_operations = { .lookup = ubifs_lookup, .create = ubifs_create, @@ -1654,7 +1646,6 @@ const struct file_operations ubifs_dir_operations = { .iterate_shared = ubifs_readdir, .fsync = ubifs_fsync, .unlocked_ioctl = ubifs_ioctl, - .open = ubifs_dir_open, #ifdef CONFIG_COMPAT .compat_ioctl = ubifs_compat_ioctl, #endif From 7081792afec4757cb4832acc70fe7edffff77771 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 2 Dec 2020 18:20:36 -0800 Subject: [PATCH 11/20] ext4: don't call fscrypt_get_encryption_info() from dx_show_leaf() The call to fscrypt_get_encryption_info() in dx_show_leaf() is too low in the call tree; fscrypt_get_encryption_info() should have already been called when starting the directory operation. And indeed, it already is. Moreover, the encryption key is guaranteed to already be available because dx_show_leaf() is only called when adding a new directory entry. And even if the key wasn't available, dx_show_leaf() uses fscrypt_fname_disk_to_usr() which knows how to create a no-key name. So for the above reasons, and because it would be desirable to stop exporting fscrypt_get_encryption_info() directly to filesystems, remove the call to fscrypt_get_encryption_info() from dx_show_leaf(). Reviewed-by: Andreas Dilger Link: https://lore.kernel.org/r/20201203022041.230976-5-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/ext4/namei.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 1e75d88e5d82..68208baca268 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -643,13 +643,7 @@ static struct stats dx_show_leaf(struct inode *dir, name = de->name; len = de->name_len; - if (IS_ENCRYPTED(dir)) - res = fscrypt_get_encryption_info(dir); - if (res) { - printk(KERN_WARNING "Error setting up" - " fname crypto: %d\n", res); - } - if (!fscrypt_has_encryption_key(dir)) { + if (!IS_ENCRYPTED(dir)) { /* Directory is not encrypted */ ext4fs_dirhash(dir, de->name, de->name_len, &h); From 4af7c34103ad54a182474620d8e984ceb7fc75dd Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 2 Dec 2020 18:20:37 -0800 Subject: [PATCH 12/20] fscrypt: introduce fscrypt_prepare_readdir() The last remaining use of fscrypt_get_encryption_info() from filesystems is for readdir (->iterate_shared()). Every other call is now in fs/crypto/ as part of some other higher-level operation. We need to add a new argument to fscrypt_get_encryption_info() to indicate whether the encryption policy is allowed to be unrecognized or not. Doing this is easier if we can work with high-level operations rather than direct filesystem use of fscrypt_get_encryption_info(). So add a function fscrypt_prepare_readdir() which wraps the call to fscrypt_get_encryption_info() for the readdir use case. Reviewed-by: Andreas Dilger Link: https://lore.kernel.org/r/20201203022041.230976-6-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/hooks.c | 6 ++++++ fs/ext4/dir.c | 8 +++----- fs/ext4/namei.c | 2 +- fs/f2fs/dir.c | 2 +- fs/ubifs/dir.c | 2 +- include/linux/fscrypt.h | 24 ++++++++++++++++++++++++ 6 files changed, 36 insertions(+), 8 deletions(-) diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c index ba1101bb46a7..0af4015a08e4 100644 --- a/fs/crypto/hooks.c +++ b/fs/crypto/hooks.c @@ -113,6 +113,12 @@ int __fscrypt_prepare_lookup(struct inode *dir, struct dentry *dentry, } EXPORT_SYMBOL_GPL(__fscrypt_prepare_lookup); +int __fscrypt_prepare_readdir(struct inode *dir) +{ + return fscrypt_get_encryption_info(dir); +} +EXPORT_SYMBOL_GPL(__fscrypt_prepare_readdir); + /** * fscrypt_prepare_setflags() - prepare to change flags with FS_IOC_SETFLAGS * @inode: the inode on which flags are being changed diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c index 7e9ee191c445..5ed870614c8d 100644 --- a/fs/ext4/dir.c +++ b/fs/ext4/dir.c @@ -118,11 +118,9 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx) struct buffer_head *bh = NULL; struct fscrypt_str fstr = FSTR_INIT(NULL, 0); - if (IS_ENCRYPTED(inode)) { - err = fscrypt_get_encryption_info(inode); - if (err) - return err; - } + err = fscrypt_prepare_readdir(inode); + if (err) + return err; if (is_dx_dir(inode)) { err = ext4_dx_readdir(file, ctx); diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 68208baca268..326fe402e495 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -1004,7 +1004,7 @@ static int htree_dirblock_to_tree(struct file *dir_file, EXT4_DIR_REC_LEN(0)); /* Check if the directory is encrypted */ if (IS_ENCRYPTED(dir)) { - err = fscrypt_get_encryption_info(dir); + err = fscrypt_prepare_readdir(dir); if (err < 0) { brelse(bh); return err; diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c index caf203cd61bd..e6270a867be1 100644 --- a/fs/f2fs/dir.c +++ b/fs/f2fs/dir.c @@ -1076,7 +1076,7 @@ static int f2fs_readdir(struct file *file, struct dir_context *ctx) int err = 0; if (IS_ENCRYPTED(inode)) { - err = fscrypt_get_encryption_info(inode); + err = fscrypt_prepare_readdir(inode); if (err) goto out; diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index a9a2e7b6a036..7949d7c9aa8c 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -515,7 +515,7 @@ static int ubifs_readdir(struct file *file, struct dir_context *ctx) return 0; if (encrypted) { - err = fscrypt_get_encryption_info(dir); + err = fscrypt_prepare_readdir(dir); if (err) return err; diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 197811a1bbd3..7f8fe0f60e5a 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -242,6 +242,7 @@ int __fscrypt_prepare_rename(struct inode *old_dir, struct dentry *old_dentry, unsigned int flags); int __fscrypt_prepare_lookup(struct inode *dir, struct dentry *dentry, struct fscrypt_name *fname); +int __fscrypt_prepare_readdir(struct inode *dir); int fscrypt_prepare_setflags(struct inode *inode, unsigned int oldflags, unsigned int flags); int fscrypt_prepare_symlink(struct inode *dir, const char *target, @@ -537,6 +538,11 @@ static inline int __fscrypt_prepare_lookup(struct inode *dir, return -EOPNOTSUPP; } +static inline int __fscrypt_prepare_readdir(struct inode *dir) +{ + return -EOPNOTSUPP; +} + static inline int fscrypt_prepare_setflags(struct inode *inode, unsigned int oldflags, unsigned int flags) @@ -798,6 +804,24 @@ static inline int fscrypt_prepare_lookup(struct inode *dir, return 0; } +/** + * fscrypt_prepare_readdir() - prepare to read a possibly-encrypted directory + * @dir: the directory inode + * + * If the directory is encrypted and it doesn't already have its encryption key + * set up, try to set it up so that the filenames will be listed in plaintext + * form rather than in no-key form. + * + * Return: 0 on success; -errno on error. Note that the encryption key being + * unavailable is not considered an error. + */ +static inline int fscrypt_prepare_readdir(struct inode *dir) +{ + if (IS_ENCRYPTED(dir)) + return __fscrypt_prepare_readdir(dir); + return 0; +} + /** * fscrypt_prepare_setattr() - prepare to change a possibly-encrypted inode's * attributes From 9aa0e0ab0dce0755c85969d757ea524d5fd69b2e Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 2 Dec 2020 18:20:38 -0800 Subject: [PATCH 13/20] fscrypt: move body of fscrypt_prepare_setattr() out-of-line In preparation for reducing the visibility of fscrypt_require_key() by moving it to fscrypt_private.h, move the call to it from fscrypt_prepare_setattr() to an out-of-line function. Reviewed-by: Andreas Dilger Link: https://lore.kernel.org/r/20201203022041.230976-7-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/hooks.c | 8 ++++++++ include/linux/fscrypt.h | 11 +++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c index 0af4015a08e4..261eb3107eed 100644 --- a/fs/crypto/hooks.c +++ b/fs/crypto/hooks.c @@ -119,6 +119,14 @@ int __fscrypt_prepare_readdir(struct inode *dir) } EXPORT_SYMBOL_GPL(__fscrypt_prepare_readdir); +int __fscrypt_prepare_setattr(struct dentry *dentry, struct iattr *attr) +{ + if (attr->ia_valid & ATTR_SIZE) + return fscrypt_require_key(d_inode(dentry)); + return 0; +} +EXPORT_SYMBOL_GPL(__fscrypt_prepare_setattr); + /** * fscrypt_prepare_setflags() - prepare to change flags with FS_IOC_SETFLAGS * @inode: the inode on which flags are being changed diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 7f8fe0f60e5a..a40f652078b7 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -243,6 +243,7 @@ int __fscrypt_prepare_rename(struct inode *old_dir, struct dentry *old_dentry, int __fscrypt_prepare_lookup(struct inode *dir, struct dentry *dentry, struct fscrypt_name *fname); int __fscrypt_prepare_readdir(struct inode *dir); +int __fscrypt_prepare_setattr(struct dentry *dentry, struct iattr *attr); int fscrypt_prepare_setflags(struct inode *inode, unsigned int oldflags, unsigned int flags); int fscrypt_prepare_symlink(struct inode *dir, const char *target, @@ -543,6 +544,12 @@ static inline int __fscrypt_prepare_readdir(struct inode *dir) return -EOPNOTSUPP; } +static inline int __fscrypt_prepare_setattr(struct dentry *dentry, + struct iattr *attr) +{ + return -EOPNOTSUPP; +} + static inline int fscrypt_prepare_setflags(struct inode *inode, unsigned int oldflags, unsigned int flags) @@ -843,8 +850,8 @@ static inline int fscrypt_prepare_readdir(struct inode *dir) static inline int fscrypt_prepare_setattr(struct dentry *dentry, struct iattr *attr) { - if (attr->ia_valid & ATTR_SIZE) - return fscrypt_require_key(d_inode(dentry)); + if (IS_ENCRYPTED(d_inode(dentry))) + return __fscrypt_prepare_setattr(dentry, attr); return 0; } From 0f25cf6b9244d31e3f5b085c91cfe78f0b9a15ad Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 2 Dec 2020 18:20:39 -0800 Subject: [PATCH 14/20] fscrypt: move fscrypt_require_key() to fscrypt_private.h fscrypt_require_key() is now only used by files in fs/crypto/. So reduce its visibility to fscrypt_private.h. This is also a prerequsite for unexporting fscrypt_get_encryption_info(). Reviewed-by: Andreas Dilger Link: https://lore.kernel.org/r/20201203022041.230976-8-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/fscrypt_private.h | 26 ++++++++++++++++++++++++++ include/linux/fscrypt.h | 26 -------------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h index 9cb9674524af..ba6404c63c4c 100644 --- a/fs/crypto/fscrypt_private.h +++ b/fs/crypto/fscrypt_private.h @@ -570,6 +570,32 @@ int fscrypt_derive_dirhash_key(struct fscrypt_info *ci, void fscrypt_hash_inode_number(struct fscrypt_info *ci, const struct fscrypt_master_key *mk); +/** + * fscrypt_require_key() - require an inode's encryption key + * @inode: the inode we need the key for + * + * If the inode is encrypted, set up its encryption key if not already done. + * Then require that the key be present and return -ENOKEY otherwise. + * + * No locks are needed, and the key will live as long as the struct inode --- so + * it won't go away from under you. + * + * Return: 0 on success, -ENOKEY if the key is missing, or another -errno code + * if a problem occurred while setting up the encryption key. + */ +static inline int fscrypt_require_key(struct inode *inode) +{ + if (IS_ENCRYPTED(inode)) { + int err = fscrypt_get_encryption_info(inode); + + if (err) + return err; + if (!fscrypt_has_encryption_key(inode)) + return -ENOKEY; + } + return 0; +} + /* keysetup_v1.c */ void fscrypt_put_direct_key(struct fscrypt_direct_key *dk); diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index a40f652078b7..febb6fd14abe 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -688,32 +688,6 @@ static inline bool fscrypt_has_encryption_key(const struct inode *inode) return fscrypt_get_info(inode) != NULL; } -/** - * fscrypt_require_key() - require an inode's encryption key - * @inode: the inode we need the key for - * - * If the inode is encrypted, set up its encryption key if not already done. - * Then require that the key be present and return -ENOKEY otherwise. - * - * No locks are needed, and the key will live as long as the struct inode --- so - * it won't go away from under you. - * - * Return: 0 on success, -ENOKEY if the key is missing, or another -errno code - * if a problem occurred while setting up the encryption key. - */ -static inline int fscrypt_require_key(struct inode *inode) -{ - if (IS_ENCRYPTED(inode)) { - int err = fscrypt_get_encryption_info(inode); - - if (err) - return err; - if (!fscrypt_has_encryption_key(inode)) - return -ENOKEY; - } - return 0; -} - /** * fscrypt_prepare_link() - prepare to link an inode into a possibly-encrypted * directory From 94bb3de392ecd057841d1af0981e1744eadbe37e Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 2 Dec 2020 18:20:40 -0800 Subject: [PATCH 15/20] fscrypt: unexport fscrypt_get_encryption_info() Now that fscrypt_get_encryption_info() is only called from files in fs/crypto/ (due to all key setup now being handled by higher-level helper functions instead of directly by filesystems), unexport it and move its declaration to fscrypt_private.h. Reviewed-by: Andreas Dilger Link: https://lore.kernel.org/r/20201203022041.230976-9-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/fscrypt_private.h | 2 ++ fs/crypto/keysetup.c | 1 - include/linux/fscrypt.h | 7 +------ 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h index ba6404c63c4c..dec47a0e0814 100644 --- a/fs/crypto/fscrypt_private.h +++ b/fs/crypto/fscrypt_private.h @@ -570,6 +570,8 @@ int fscrypt_derive_dirhash_key(struct fscrypt_info *ci, void fscrypt_hash_inode_number(struct fscrypt_info *ci, const struct fscrypt_master_key *mk); +int fscrypt_get_encryption_info(struct inode *inode); + /** * fscrypt_require_key() - require an inode's encryption key * @inode: the inode we need the key for diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c index 50675b42d5b7..6339b3069a40 100644 --- a/fs/crypto/keysetup.c +++ b/fs/crypto/keysetup.c @@ -589,7 +589,6 @@ int fscrypt_get_encryption_info(struct inode *inode) res = 0; return res; } -EXPORT_SYMBOL(fscrypt_get_encryption_info); /** * fscrypt_prepare_new_inode() - prepare to create a new inode in a directory diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index febb6fd14abe..103e2b2dd84a 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -75,7 +75,7 @@ struct fscrypt_operations { static inline struct fscrypt_info *fscrypt_get_info(const struct inode *inode) { /* - * Pairs with the cmpxchg_release() in fscrypt_get_encryption_info(). + * Pairs with the cmpxchg_release() in fscrypt_setup_encryption_info(). * I.e., another task may publish ->i_crypt_info concurrently, executing * a RELEASE barrier. We need to use smp_load_acquire() here to safely * ACQUIRE the memory the other task published. @@ -200,7 +200,6 @@ int fscrypt_ioctl_remove_key_all_users(struct file *filp, void __user *arg); int fscrypt_ioctl_get_key_status(struct file *filp, void __user *arg); /* keysetup.c */ -int fscrypt_get_encryption_info(struct inode *inode); int fscrypt_prepare_new_inode(struct inode *dir, struct inode *inode, bool *encrypt_ret); void fscrypt_put_encryption_info(struct inode *inode); @@ -408,10 +407,6 @@ static inline int fscrypt_ioctl_get_key_status(struct file *filp, } /* keysetup.c */ -static inline int fscrypt_get_encryption_info(struct inode *inode) -{ - return -EOPNOTSUPP; -} static inline int fscrypt_prepare_new_inode(struct inode *dir, struct inode *inode, From a7359960b6d27426b2d4f20bd9bd1d1033ebe5af Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 2 Dec 2020 18:20:41 -0800 Subject: [PATCH 16/20] fscrypt: allow deleting files with unsupported encryption policy Currently it's impossible to delete files that use an unsupported encryption policy, as the kernel will just return an error when performing any operation on the top-level encrypted directory, even just a path lookup into the directory or opening the directory for readdir. More specifically, this occurs in any of the following cases: - The encryption context has an unrecognized version number. Current kernels know about v1 and v2, but there could be more versions in the future. - The encryption context has unrecognized encryption modes (FSCRYPT_MODE_*) or flags (FSCRYPT_POLICY_FLAG_*), an unrecognized combination of modes, or reserved bits set. - The encryption key has been added and the encryption modes are recognized but aren't available in the crypto API -- for example, a directory is encrypted with FSCRYPT_MODE_ADIANTUM but the kernel doesn't have CONFIG_CRYPTO_ADIANTUM enabled. It's desirable to return errors for most operations on files that use an unsupported encryption policy, but the current behavior is too strict. We need to allow enough to delete files, so that people can't be stuck with undeletable files when downgrading kernel versions. That includes allowing directories to be listed and allowing dentries to be looked up. Fix this by modifying the key setup logic to treat an unsupported encryption policy in the same way as "key unavailable" in the cases that are required for a recursive delete to work: preparing for a readdir or a dentry lookup, revalidating a dentry, or checking whether an inode has the same encryption policy as its parent directory. Reviewed-by: Andreas Dilger Link: https://lore.kernel.org/r/20201203022041.230976-10-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/fname.c | 8 ++++++-- fs/crypto/fscrypt_private.h | 4 ++-- fs/crypto/hooks.c | 4 ++-- fs/crypto/keysetup.c | 19 +++++++++++++++++-- fs/crypto/policy.c | 22 ++++++++++++++-------- include/linux/fscrypt.h | 9 ++++++--- 6 files changed, 47 insertions(+), 19 deletions(-) diff --git a/fs/crypto/fname.c b/fs/crypto/fname.c index cb3cfa6329ba..f3cd592f5171 100644 --- a/fs/crypto/fname.c +++ b/fs/crypto/fname.c @@ -404,7 +404,7 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname, fname->disk_name.len = iname->len; return 0; } - ret = fscrypt_get_encryption_info(dir); + ret = fscrypt_get_encryption_info(dir, lookup); if (ret) return ret; @@ -560,7 +560,11 @@ int fscrypt_d_revalidate(struct dentry *dentry, unsigned int flags) return -ECHILD; dir = dget_parent(dentry); - err = fscrypt_get_encryption_info(d_inode(dir)); + /* + * Pass allow_unsupported=true, so that files with an unsupported + * encryption policy can be deleted. + */ + err = fscrypt_get_encryption_info(d_inode(dir), true); valid = !fscrypt_has_encryption_key(d_inode(dir)); dput(dir); diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h index dec47a0e0814..3fa965eb3336 100644 --- a/fs/crypto/fscrypt_private.h +++ b/fs/crypto/fscrypt_private.h @@ -570,7 +570,7 @@ int fscrypt_derive_dirhash_key(struct fscrypt_info *ci, void fscrypt_hash_inode_number(struct fscrypt_info *ci, const struct fscrypt_master_key *mk); -int fscrypt_get_encryption_info(struct inode *inode); +int fscrypt_get_encryption_info(struct inode *inode, bool allow_unsupported); /** * fscrypt_require_key() - require an inode's encryption key @@ -588,7 +588,7 @@ int fscrypt_get_encryption_info(struct inode *inode); static inline int fscrypt_require_key(struct inode *inode) { if (IS_ENCRYPTED(inode)) { - int err = fscrypt_get_encryption_info(inode); + int err = fscrypt_get_encryption_info(inode, false); if (err) return err; diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c index 261eb3107eed..a73b0376e6f3 100644 --- a/fs/crypto/hooks.c +++ b/fs/crypto/hooks.c @@ -115,7 +115,7 @@ EXPORT_SYMBOL_GPL(__fscrypt_prepare_lookup); int __fscrypt_prepare_readdir(struct inode *dir) { - return fscrypt_get_encryption_info(dir); + return fscrypt_get_encryption_info(dir, true); } EXPORT_SYMBOL_GPL(__fscrypt_prepare_readdir); @@ -331,7 +331,7 @@ const char *fscrypt_get_symlink(struct inode *inode, const void *caddr, * Try to set up the symlink's encryption key, but we can continue * regardless of whether the key is available or not. */ - err = fscrypt_get_encryption_info(inode); + err = fscrypt_get_encryption_info(inode, false); if (err) return ERR_PTR(err); has_key = fscrypt_has_encryption_key(inode); diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c index 6339b3069a40..261293fb7097 100644 --- a/fs/crypto/keysetup.c +++ b/fs/crypto/keysetup.c @@ -546,6 +546,11 @@ fscrypt_setup_encryption_info(struct inode *inode, /** * fscrypt_get_encryption_info() - set up an inode's encryption key * @inode: the inode to set up the key for. Must be encrypted. + * @allow_unsupported: if %true, treat an unsupported encryption policy (or + * unrecognized encryption context) the same way as the key + * being unavailable, instead of returning an error. Use + * %false unless the operation being performed is needed in + * order for files (or directories) to be deleted. * * Set up ->i_crypt_info, if it hasn't already been done. * @@ -556,7 +561,7 @@ fscrypt_setup_encryption_info(struct inode *inode, * encryption key is unavailable. (Use fscrypt_has_encryption_key() to * distinguish these cases.) Also can return another -errno code. */ -int fscrypt_get_encryption_info(struct inode *inode) +int fscrypt_get_encryption_info(struct inode *inode, bool allow_unsupported) { int res; union fscrypt_context ctx; @@ -567,24 +572,34 @@ int fscrypt_get_encryption_info(struct inode *inode) res = inode->i_sb->s_cop->get_context(inode, &ctx, sizeof(ctx)); if (res < 0) { + if (res == -ERANGE && allow_unsupported) + return 0; fscrypt_warn(inode, "Error %d getting encryption context", res); return res; } res = fscrypt_policy_from_context(&policy, &ctx, res); if (res) { + if (allow_unsupported) + return 0; fscrypt_warn(inode, "Unrecognized or corrupt encryption context"); return res; } - if (!fscrypt_supported_policy(&policy, inode)) + if (!fscrypt_supported_policy(&policy, inode)) { + if (allow_unsupported) + return 0; return -EINVAL; + } res = fscrypt_setup_encryption_info(inode, &policy, fscrypt_context_nonce(&ctx), IS_CASEFOLDED(inode) && S_ISDIR(inode->i_mode)); + + if (res == -ENOPKG && allow_unsupported) /* Algorithm unavailable? */ + res = 0; if (res == -ENOKEY) res = 0; return res; diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c index faa0f21daa68..a51cef6bd27f 100644 --- a/fs/crypto/policy.c +++ b/fs/crypto/policy.c @@ -590,7 +590,7 @@ EXPORT_SYMBOL_GPL(fscrypt_ioctl_get_nonce); int fscrypt_has_permitted_context(struct inode *parent, struct inode *child) { union fscrypt_policy parent_policy, child_policy; - int err; + int err, err1, err2; /* No restrictions on file types which are never encrypted */ if (!S_ISREG(child->i_mode) && !S_ISDIR(child->i_mode) && @@ -620,19 +620,25 @@ int fscrypt_has_permitted_context(struct inode *parent, struct inode *child) * In any case, if an unexpected error occurs, fall back to "forbidden". */ - err = fscrypt_get_encryption_info(parent); + err = fscrypt_get_encryption_info(parent, true); if (err) return 0; - err = fscrypt_get_encryption_info(child); + err = fscrypt_get_encryption_info(child, true); if (err) return 0; - err = fscrypt_get_policy(parent, &parent_policy); - if (err) - return 0; + err1 = fscrypt_get_policy(parent, &parent_policy); + err2 = fscrypt_get_policy(child, &child_policy); - err = fscrypt_get_policy(child, &child_policy); - if (err) + /* + * Allow the case where the parent and child both have an unrecognized + * encryption policy, so that files with an unrecognized encryption + * policy can be deleted. + */ + if (err1 == -EINVAL && err2 == -EINVAL) + return 1; + + if (err1 || err2) return 0; return fscrypt_policies_equal(&parent_policy, &child_policy); diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 103e2b2dd84a..2ea1387bb497 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -753,8 +753,9 @@ static inline int fscrypt_prepare_rename(struct inode *old_dir, * * Prepare for ->lookup() in a directory which may be encrypted by determining * the name that will actually be used to search the directory on-disk. If the - * directory's encryption key is available, then the lookup is assumed to be by - * plaintext name; otherwise, it is assumed to be by no-key name. + * directory's encryption policy is supported by this kernel and its encryption + * key is available, then the lookup is assumed to be by plaintext name; + * otherwise, it is assumed to be by no-key name. * * This will set DCACHE_NOKEY_NAME on the dentry if the lookup is by no-key * name. In this case the filesystem must assign the dentry a dentry_operations @@ -789,7 +790,9 @@ static inline int fscrypt_prepare_lookup(struct inode *dir, * form rather than in no-key form. * * Return: 0 on success; -errno on error. Note that the encryption key being - * unavailable is not considered an error. + * unavailable is not considered an error. It is also not an error if + * the encryption policy is unsupported by this kernel; that is treated + * like the key being unavailable, so that files can still be deleted. */ static inline int fscrypt_prepare_readdir(struct inode *dir) { From c864727cb6d29c737d0a12c2c30e8b6e6d63037e Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 13 Nov 2020 13:19:15 -0800 Subject: [PATCH 17/20] fs-verity: remove filenames from file comments Embedding the file path inside kernel source code files isn't particularly useful as often files are moved around and the paths become incorrect. checkpatch.pl warns about this since v5.10-rc1. Acked-by: Luca Boccassi Link: https://lore.kernel.org/r/20201113211918.71883-2-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/verity/enable.c | 2 +- fs/verity/hash_algs.c | 2 +- fs/verity/init.c | 2 +- fs/verity/measure.c | 2 +- fs/verity/open.c | 2 +- fs/verity/signature.c | 2 +- fs/verity/verify.c | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fs/verity/enable.c b/fs/verity/enable.c index 5ab3bbec8108..9c5b28c86522 100644 --- a/fs/verity/enable.c +++ b/fs/verity/enable.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * fs/verity/enable.c: ioctl to enable verity on a file + * Ioctl to enable verity on a file * * Copyright 2019 Google LLC */ diff --git a/fs/verity/hash_algs.c b/fs/verity/hash_algs.c index c37e186ebeb6..71d0fccb6d4c 100644 --- a/fs/verity/hash_algs.c +++ b/fs/verity/hash_algs.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * fs/verity/hash_algs.c: fs-verity hash algorithms + * fs-verity hash algorithms * * Copyright 2019 Google LLC */ diff --git a/fs/verity/init.c b/fs/verity/init.c index 94c104e00861..c98b7016f446 100644 --- a/fs/verity/init.c +++ b/fs/verity/init.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * fs/verity/init.c: fs-verity module initialization and logging + * fs-verity module initialization and logging * * Copyright 2019 Google LLC */ diff --git a/fs/verity/measure.c b/fs/verity/measure.c index df409a5682ed..5300b8d38537 100644 --- a/fs/verity/measure.c +++ b/fs/verity/measure.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * fs/verity/measure.c: ioctl to get a verity file's measurement + * Ioctl to get a verity file's measurement * * Copyright 2019 Google LLC */ diff --git a/fs/verity/open.c b/fs/verity/open.c index bfe0280c14e4..a28d5be78a09 100644 --- a/fs/verity/open.c +++ b/fs/verity/open.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * fs/verity/open.c: opening fs-verity files + * Opening fs-verity files * * Copyright 2019 Google LLC */ diff --git a/fs/verity/signature.c b/fs/verity/signature.c index b14ed96387ec..12794a4dd158 100644 --- a/fs/verity/signature.c +++ b/fs/verity/signature.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * fs/verity/signature.c: verification of builtin signatures + * Verification of builtin signatures * * Copyright 2019 Google LLC */ diff --git a/fs/verity/verify.c b/fs/verity/verify.c index a8b68c6f663d..0adb970f4e73 100644 --- a/fs/verity/verify.c +++ b/fs/verity/verify.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * fs/verity/verify.c: data verification functions, i.e. hooks for ->readpages() + * Data verification functions, i.e. hooks for ->readpages() * * Copyright 2019 Google LLC */ From 7dcf6d6887e289c97a06b9dd7fc33b62b5259db1 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 13 Nov 2020 13:19:16 -0800 Subject: [PATCH 18/20] fs-verity: rename fsverity_signed_digest to fsverity_formatted_digest The name "struct fsverity_signed_digest" is causing confusion because it isn't actually a signed digest, but rather it's the way that the digest is formatted in order to be signed. Rename it to "struct fsverity_formatted_digest" to prevent this confusion. Also update the struct's comment to clarify that it's specific to the built-in signature verification support and isn't a requirement for all fs-verity users. I'll be renaming this struct in fsverity-utils too. Acked-by: Luca Boccassi Link: https://lore.kernel.org/r/20201113211918.71883-3-ebiggers@kernel.org Signed-off-by: Eric Biggers --- Documentation/filesystems/fsverity.rst | 2 +- fs/verity/fsverity_private.h | 17 ++++++++++++----- fs/verity/signature.c | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Documentation/filesystems/fsverity.rst b/Documentation/filesystems/fsverity.rst index 895e9711ed88..421b75498d49 100644 --- a/Documentation/filesystems/fsverity.rst +++ b/Documentation/filesystems/fsverity.rst @@ -372,7 +372,7 @@ kernel. Specifically, it adds support for: File measurements must be signed in the following format, which is similar to the structure used by `FS_IOC_MEASURE_VERITY`_:: - struct fsverity_signed_digest { + struct fsverity_formatted_digest { char magic[8]; /* must be "FSVerity" */ __le16 digest_algorithm; __le16 digest_size; diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h index e96d99d5145e..75f8e18b44a5 100644 --- a/fs/verity/fsverity_private.h +++ b/fs/verity/fsverity_private.h @@ -101,12 +101,19 @@ struct fsverity_descriptor { sizeof(struct fsverity_descriptor)) /* - * Format in which verity file measurements are signed. This is the same as - * 'struct fsverity_digest', except here some magic bytes are prepended to - * provide some context about what is being signed in case the same key is used - * for non-fsverity purposes, and here the fields have fixed endianness. + * Format in which verity file measurements are signed in built-in signatures. + * This is the same as 'struct fsverity_digest', except here some magic bytes + * are prepended to provide some context about what is being signed in case the + * same key is used for non-fsverity purposes, and here the fields have fixed + * endianness. + * + * This struct is specific to the built-in signature verification support, which + * is optional. fs-verity users may also verify signatures in userspace, in + * which case userspace is responsible for deciding on what bytes are signed. + * This struct may still be used, but it doesn't have to be. For example, + * userspace could instead use a string like "sha256:$digest_as_hex_string". */ -struct fsverity_signed_digest { +struct fsverity_formatted_digest { char magic[8]; /* must be "FSVerity" */ __le16 digest_algorithm; __le16 digest_size; diff --git a/fs/verity/signature.c b/fs/verity/signature.c index 12794a4dd158..74ae10f04d21 100644 --- a/fs/verity/signature.c +++ b/fs/verity/signature.c @@ -44,7 +44,7 @@ int fsverity_verify_signature(const struct fsverity_info *vi, const struct inode *inode = vi->inode; const struct fsverity_hash_alg *hash_alg = vi->tree_params.hash_alg; const u32 sig_size = le32_to_cpu(desc->sig_size); - struct fsverity_signed_digest *d; + struct fsverity_formatted_digest *d; int err; if (sig_size == 0) { From 5fe13f59fa42a7a9f41f27d55f0aad808faf5805 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 13 Nov 2020 13:19:17 -0800 Subject: [PATCH 19/20] fs-verity: rename "file measurement" to "file digest" I originally chose the name "file measurement" to refer to the fs-verity file digest to avoid confusion with traditional full-file digests or with the bare root hash of the Merkle tree. But the name "file measurement" hasn't caught on, and usually people are calling it something else, usually the "file digest". E.g. see "struct fsverity_digest" and "struct fsverity_formatted_digest", the libfsverity_compute_digest() and libfsverity_sign_digest() functions in libfsverity, and the "fsverity digest" command. Having multiple names for the same thing is always confusing. So to hopefully avoid confusion in the future, rename "fs-verity file measurement" to "fs-verity file digest". This leaves FS_IOC_MEASURE_VERITY as the only reference to "measure" in the kernel, which makes some amount of sense since the ioctl is actively "measuring" the file. I'll be renaming this in fsverity-utils too (though similarly the 'fsverity measure' command, which is a wrapper for FS_IOC_MEASURE_VERITY, will stay). Acked-by: Luca Boccassi Link: https://lore.kernel.org/r/20201113211918.71883-4-ebiggers@kernel.org Signed-off-by: Eric Biggers --- Documentation/filesystems/fsverity.rst | 60 +++++++++++++------------- fs/verity/enable.c | 6 +-- fs/verity/fsverity_private.h | 12 +++--- fs/verity/measure.c | 12 +++--- fs/verity/open.c | 22 +++++----- fs/verity/signature.c | 10 ++--- 6 files changed, 61 insertions(+), 61 deletions(-) diff --git a/Documentation/filesystems/fsverity.rst b/Documentation/filesystems/fsverity.rst index 421b75498d49..2eee558b7f5f 100644 --- a/Documentation/filesystems/fsverity.rst +++ b/Documentation/filesystems/fsverity.rst @@ -27,9 +27,9 @@ automatically verified against the file's Merkle tree. Reads of any corrupted data, including mmap reads, will fail. Userspace can use another ioctl to retrieve the root hash (actually -the "file measurement", which is a hash that includes the root hash) -that fs-verity is enforcing for the file. This ioctl executes in -constant time, regardless of the file size. +the "fs-verity file digest", which is a hash that includes the Merkle +tree root hash) that fs-verity is enforcing for the file. This ioctl +executes in constant time, regardless of the file size. fs-verity is essentially a way to hash a file in constant time, subject to the caveat that reads which would violate the hash will @@ -177,9 +177,10 @@ FS_IOC_ENABLE_VERITY can fail with the following errors: FS_IOC_MEASURE_VERITY --------------------- -The FS_IOC_MEASURE_VERITY ioctl retrieves the measurement of a verity -file. The file measurement is a digest that cryptographically -identifies the file contents that are being enforced on reads. +The FS_IOC_MEASURE_VERITY ioctl retrieves the digest of a verity file. +The fs-verity file digest is a cryptographic digest that identifies +the file contents that are being enforced on reads; it is computed via +a Merkle tree and is different from a traditional full-file digest. This ioctl takes in a pointer to a variable-length structure:: @@ -197,7 +198,7 @@ On success, 0 is returned and the kernel fills in the structure as follows: - ``digest_algorithm`` will be the hash algorithm used for the file - measurement. It will match ``fsverity_enable_arg::hash_algorithm``. + digest. It will match ``fsverity_enable_arg::hash_algorithm``. - ``digest_size`` will be the size of the digest in bytes, e.g. 32 for SHA-256. (This can be redundant with ``digest_algorithm``.) - ``digest`` will be the actual bytes of the digest. @@ -257,25 +258,24 @@ non-verity one, with the following exceptions: with EIO (for read()) or SIGBUS (for mmap() reads). - If the sysctl "fs.verity.require_signatures" is set to 1 and the - file's verity measurement is not signed by a key in the fs-verity - keyring, then opening the file will fail. See `Built-in signature - verification`_. + file is not signed by a key in the fs-verity keyring, then opening + the file will fail. See `Built-in signature verification`_. Direct access to the Merkle tree is not supported. Therefore, if a verity file is copied, or is backed up and restored, then it will lose its "verity"-ness. fs-verity is primarily meant for files like executables that are managed by a package manager. -File measurement computation -============================ +File digest computation +======================= This section describes how fs-verity hashes the file contents using a -Merkle tree to produce the "file measurement" which cryptographically -identifies the file contents. This algorithm is the same for all -filesystems that support fs-verity. +Merkle tree to produce the digest which cryptographically identifies +the file contents. This algorithm is the same for all filesystems +that support fs-verity. Userspace only needs to be aware of this algorithm if it needs to -compute the file measurement itself, e.g. in order to sign the file. +compute fs-verity file digests itself, e.g. in order to sign files. .. _fsverity_merkle_tree: @@ -325,9 +325,9 @@ can't a distinguish a large file from a small second file whose data is exactly the top-level hash block of the first file. Ambiguities also arise from the convention of padding to the next block boundary. -To solve this problem, the verity file measurement is actually -computed as a hash of the following structure, which contains the -Merkle tree root hash as well as other fields such as the file size:: +To solve this problem, the fs-verity file digest is actually computed +as a hash of the following structure, which contains the Merkle tree +root hash as well as other fields such as the file size:: struct fsverity_descriptor { __u8 version; /* must be 1 */ @@ -359,18 +359,18 @@ kernel. Specifically, it adds support for: certificates from being added. 2. `FS_IOC_ENABLE_VERITY`_ accepts a pointer to a PKCS#7 formatted - detached signature in DER format of the file measurement. On - success, this signature is persisted alongside the Merkle tree. + detached signature in DER format of the file's fs-verity digest. + On success, this signature is persisted alongside the Merkle tree. Then, any time the file is opened, the kernel will verify the - file's actual measurement against this signature, using the - certificates in the ".fs-verity" keyring. + file's actual digest against this signature, using the certificates + in the ".fs-verity" keyring. 3. A new sysctl "fs.verity.require_signatures" is made available. When set to 1, the kernel requires that all verity files have a - correctly signed file measurement as described in (2). + correctly signed digest as described in (2). -File measurements must be signed in the following format, which is -similar to the structure used by `FS_IOC_MEASURE_VERITY`_:: +fs-verity file digests must be signed in the following format, which +is similar to the structure used by `FS_IOC_MEASURE_VERITY`_:: struct fsverity_formatted_digest { char magic[8]; /* must be "FSVerity" */ @@ -421,8 +421,8 @@ can only be set by `FS_IOC_ENABLE_VERITY`_, and it cannot be cleared. ext4 also supports encryption, which can be used simultaneously with fs-verity. In this case, the plaintext data is verified rather than -the ciphertext. This is necessary in order to make the file -measurement meaningful, since every file is encrypted differently. +the ciphertext. This is necessary in order to make the fs-verity file +digest meaningful, since every file is encrypted differently. ext4 stores the verity metadata (Merkle tree and fsverity_descriptor) past the end of the file, starting at the first 64K boundary beyond @@ -592,8 +592,8 @@ weren't already directly answered in other parts of this document. :Q: Isn't fs-verity useless because the attacker can just modify the hashes in the Merkle tree, which is stored on-disk? :A: To verify the authenticity of an fs-verity file you must verify - the authenticity of the "file measurement", which is basically the - root hash of the Merkle tree. See `Use cases`_. + the authenticity of the "fs-verity file digest", which + incorporates the root hash of the Merkle tree. See `Use cases`_. :Q: Isn't fs-verity useless because the attacker can just replace a verity file with a non-verity one? diff --git a/fs/verity/enable.c b/fs/verity/enable.c index 9c5b28c86522..f7e997a01ad0 100644 --- a/fs/verity/enable.c +++ b/fs/verity/enable.c @@ -398,9 +398,9 @@ int fsverity_ioctl_enable(struct file *filp, const void __user *uarg) * Some pages of the file may have been evicted from pagecache after * being used in the Merkle tree construction, then read into pagecache * again by another process reading from the file concurrently. Since - * these pages didn't undergo verification against the file measurement - * which fs-verity now claims to be enforcing, we have to wipe the - * pagecache to ensure that all future reads are verified. + * these pages didn't undergo verification against the file digest which + * fs-verity now claims to be enforcing, we have to wipe the pagecache + * to ensure that all future reads are verified. */ filemap_write_and_wait(inode->i_mapping); invalidate_inode_pages2(inode->i_mapping); diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h index 75f8e18b44a5..21e9930d65fb 100644 --- a/fs/verity/fsverity_private.h +++ b/fs/verity/fsverity_private.h @@ -67,19 +67,19 @@ struct merkle_tree_params { * When a verity file is first opened, an instance of this struct is allocated * and stored in ->i_verity_info; it remains until the inode is evicted. It * caches information about the Merkle tree that's needed to efficiently verify - * data read from the file. It also caches the file measurement. The Merkle - * tree pages themselves are not cached here, but the filesystem may cache them. + * data read from the file. It also caches the file digest. The Merkle tree + * pages themselves are not cached here, but the filesystem may cache them. */ struct fsverity_info { struct merkle_tree_params tree_params; u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE]; - u8 measurement[FS_VERITY_MAX_DIGEST_SIZE]; + u8 file_digest[FS_VERITY_MAX_DIGEST_SIZE]; const struct inode *inode; }; /* - * Merkle tree properties. The file measurement is the hash of this structure - * excluding the signature and with the sig_size field set to 0. + * Merkle tree properties. The fs-verity file digest is the hash of this + * structure excluding the signature and with the sig_size field set to 0. */ struct fsverity_descriptor { __u8 version; /* must be 1 */ @@ -101,7 +101,7 @@ struct fsverity_descriptor { sizeof(struct fsverity_descriptor)) /* - * Format in which verity file measurements are signed in built-in signatures. + * Format in which fs-verity file digests are signed in built-in signatures. * This is the same as 'struct fsverity_digest', except here some magic bytes * are prepended to provide some context about what is being signed in case the * same key is used for non-fsverity purposes, and here the fields have fixed diff --git a/fs/verity/measure.c b/fs/verity/measure.c index 5300b8d38537..f0d7b30c62db 100644 --- a/fs/verity/measure.c +++ b/fs/verity/measure.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Ioctl to get a verity file's measurement + * Ioctl to get a verity file's digest * * Copyright 2019 Google LLC */ @@ -10,12 +10,12 @@ #include /** - * fsverity_ioctl_measure() - get a verity file's measurement - * @filp: file to get measurement of + * fsverity_ioctl_measure() - get a verity file's digest + * @filp: file to get digest of * @_uarg: user pointer to fsverity_digest * - * Retrieve the file measurement that the kernel is enforcing for reads from a - * verity file. See the "FS_IOC_MEASURE_VERITY" section of + * Retrieve the file digest that the kernel is enforcing for reads from a verity + * file. See the "FS_IOC_MEASURE_VERITY" section of * Documentation/filesystems/fsverity.rst for the documentation. * * Return: 0 on success, -errno on failure @@ -51,7 +51,7 @@ int fsverity_ioctl_measure(struct file *filp, void __user *_uarg) if (copy_to_user(uarg, &arg, sizeof(arg))) return -EFAULT; - if (copy_to_user(uarg->digest, vi->measurement, hash_alg->digest_size)) + if (copy_to_user(uarg->digest, vi->file_digest, hash_alg->digest_size)) return -EFAULT; return 0; diff --git a/fs/verity/open.c b/fs/verity/open.c index a28d5be78a09..228d0eca3e2e 100644 --- a/fs/verity/open.c +++ b/fs/verity/open.c @@ -124,18 +124,18 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params, } /* - * Compute the file measurement by hashing the fsverity_descriptor excluding the + * Compute the file digest by hashing the fsverity_descriptor excluding the * signature and with the sig_size field set to 0. */ -static int compute_file_measurement(struct fsverity_hash_alg *hash_alg, - struct fsverity_descriptor *desc, - u8 *measurement) +static int compute_file_digest(struct fsverity_hash_alg *hash_alg, + struct fsverity_descriptor *desc, + u8 *file_digest) { __le32 sig_size = desc->sig_size; int err; desc->sig_size = 0; - err = fsverity_hash_buffer(hash_alg, desc, sizeof(*desc), measurement); + err = fsverity_hash_buffer(hash_alg, desc, sizeof(*desc), file_digest); desc->sig_size = sig_size; return err; @@ -199,15 +199,15 @@ struct fsverity_info *fsverity_create_info(const struct inode *inode, memcpy(vi->root_hash, desc->root_hash, vi->tree_params.digest_size); - err = compute_file_measurement(vi->tree_params.hash_alg, desc, - vi->measurement); + err = compute_file_digest(vi->tree_params.hash_alg, desc, + vi->file_digest); if (err) { - fsverity_err(inode, "Error %d computing file measurement", err); + fsverity_err(inode, "Error %d computing file digest", err); goto out; } - pr_debug("Computed file measurement: %s:%*phN\n", + pr_debug("Computed file digest: %s:%*phN\n", vi->tree_params.hash_alg->name, - vi->tree_params.digest_size, vi->measurement); + vi->tree_params.digest_size, vi->file_digest); err = fsverity_verify_signature(vi, desc, desc_size); out: @@ -354,7 +354,7 @@ int __init fsverity_init_info_cache(void) { fsverity_info_cachep = KMEM_CACHE_USERCOPY(fsverity_info, SLAB_RECLAIM_ACCOUNT, - measurement); + file_digest); if (!fsverity_info_cachep) return -ENOMEM; return 0; diff --git a/fs/verity/signature.c b/fs/verity/signature.c index 74ae10f04d21..012468eda2a7 100644 --- a/fs/verity/signature.c +++ b/fs/verity/signature.c @@ -32,8 +32,8 @@ static struct key *fsverity_keyring; * @desc: the file's fsverity_descriptor * @desc_size: size of @desc * - * If the file's fs-verity descriptor includes a signature of the file - * measurement, verify it against the certificates in the fs-verity keyring. + * If the file's fs-verity descriptor includes a signature of the file digest, + * verify it against the certificates in the fs-verity keyring. * * Return: 0 on success (signature valid or not required); -errno on failure */ @@ -67,7 +67,7 @@ int fsverity_verify_signature(const struct fsverity_info *vi, memcpy(d->magic, "FSVerity", 8); d->digest_algorithm = cpu_to_le16(hash_alg - fsverity_hash_algs); d->digest_size = cpu_to_le16(hash_alg->digest_size); - memcpy(d->digest, vi->measurement, hash_alg->digest_size); + memcpy(d->digest, vi->file_digest, hash_alg->digest_size); err = verify_pkcs7_signature(d, sizeof(*d) + hash_alg->digest_size, desc->signature, sig_size, @@ -90,8 +90,8 @@ int fsverity_verify_signature(const struct fsverity_info *vi, return err; } - pr_debug("Valid signature for file measurement %s:%*phN\n", - hash_alg->name, hash_alg->digest_size, vi->measurement); + pr_debug("Valid signature for file digest %s:%*phN\n", + hash_alg->name, hash_alg->digest_size, vi->file_digest); return 0; } From 78d21c23dc0bac261a6aca86cb3184160d9577bd Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 13 Nov 2020 13:19:18 -0800 Subject: [PATCH 20/20] fs-verity: move structs needed for file signing to UAPI header Although it isn't used directly by the ioctls, "struct fsverity_descriptor" is required by userspace programs that need to compute fs-verity file digests in a standalone way. Therefore it's also needed to sign files in a standalone way. Similarly, "struct fsverity_formatted_digest" (previously called "struct fsverity_signed_digest" which was misleading) is also needed to sign files if the built-in signature verification is being used. Therefore, move these structs to the UAPI header. While doing this, try to make it clear that the signature-related fields in fsverity_descriptor aren't used in the file digest computation. Acked-by: Luca Boccassi Link: https://lore.kernel.org/r/20201113211918.71883-5-ebiggers@kernel.org Signed-off-by: Eric Biggers --- Documentation/filesystems/fsverity.rst | 6 +--- fs/verity/fsverity_private.h | 37 ------------------- include/uapi/linux/fsverity.h | 49 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/Documentation/filesystems/fsverity.rst b/Documentation/filesystems/fsverity.rst index 2eee558b7f5f..e0204a23e997 100644 --- a/Documentation/filesystems/fsverity.rst +++ b/Documentation/filesystems/fsverity.rst @@ -334,17 +334,13 @@ root hash as well as other fields such as the file size:: __u8 hash_algorithm; /* Merkle tree hash algorithm */ __u8 log_blocksize; /* log2 of size of data and tree blocks */ __u8 salt_size; /* size of salt in bytes; 0 if none */ - __le32 sig_size; /* must be 0 */ + __le32 __reserved_0x04; /* must be 0 */ __le64 data_size; /* size of file the Merkle tree is built over */ __u8 root_hash[64]; /* Merkle tree root hash */ __u8 salt[32]; /* salt prepended to each hashed block */ __u8 __reserved[144]; /* must be 0's */ }; -Note that the ``sig_size`` field must be set to 0 for the purpose of -computing the file measurement, even if a signature was provided (or -will be provided) to `FS_IOC_ENABLE_VERITY`_. - Built-in signature verification =============================== diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h index 21e9930d65fb..96f7b332f54f 100644 --- a/fs/verity/fsverity_private.h +++ b/fs/verity/fsverity_private.h @@ -77,49 +77,12 @@ struct fsverity_info { const struct inode *inode; }; -/* - * Merkle tree properties. The fs-verity file digest is the hash of this - * structure excluding the signature and with the sig_size field set to 0. - */ -struct fsverity_descriptor { - __u8 version; /* must be 1 */ - __u8 hash_algorithm; /* Merkle tree hash algorithm */ - __u8 log_blocksize; /* log2 of size of data and tree blocks */ - __u8 salt_size; /* size of salt in bytes; 0 if none */ - __le32 sig_size; /* size of signature in bytes; 0 if none */ - __le64 data_size; /* size of file the Merkle tree is built over */ - __u8 root_hash[64]; /* Merkle tree root hash */ - __u8 salt[32]; /* salt prepended to each hashed block */ - __u8 __reserved[144]; /* must be 0's */ - __u8 signature[]; /* optional PKCS#7 signature */ -}; - /* Arbitrary limit to bound the kmalloc() size. Can be changed. */ #define FS_VERITY_MAX_DESCRIPTOR_SIZE 16384 #define FS_VERITY_MAX_SIGNATURE_SIZE (FS_VERITY_MAX_DESCRIPTOR_SIZE - \ sizeof(struct fsverity_descriptor)) -/* - * Format in which fs-verity file digests are signed in built-in signatures. - * This is the same as 'struct fsverity_digest', except here some magic bytes - * are prepended to provide some context about what is being signed in case the - * same key is used for non-fsverity purposes, and here the fields have fixed - * endianness. - * - * This struct is specific to the built-in signature verification support, which - * is optional. fs-verity users may also verify signatures in userspace, in - * which case userspace is responsible for deciding on what bytes are signed. - * This struct may still be used, but it doesn't have to be. For example, - * userspace could instead use a string like "sha256:$digest_as_hex_string". - */ -struct fsverity_formatted_digest { - char magic[8]; /* must be "FSVerity" */ - __le16 digest_algorithm; - __le16 digest_size; - __u8 digest[]; -}; - /* hash_algs.c */ extern struct fsverity_hash_alg fsverity_hash_algs[]; diff --git a/include/uapi/linux/fsverity.h b/include/uapi/linux/fsverity.h index da0daf6c193b..33f44156f8ea 100644 --- a/include/uapi/linux/fsverity.h +++ b/include/uapi/linux/fsverity.h @@ -34,6 +34,55 @@ struct fsverity_digest { __u8 digest[]; }; +/* + * Struct containing a file's Merkle tree properties. The fs-verity file digest + * is the hash of this struct. A userspace program needs this struct only if it + * needs to compute fs-verity file digests itself, e.g. in order to sign files. + * It isn't needed just to enable fs-verity on a file. + * + * Note: when computing the file digest, 'sig_size' and 'signature' must be left + * zero and empty, respectively. These fields are present only because some + * filesystems reuse this struct as part of their on-disk format. + */ +struct fsverity_descriptor { + __u8 version; /* must be 1 */ + __u8 hash_algorithm; /* Merkle tree hash algorithm */ + __u8 log_blocksize; /* log2 of size of data and tree blocks */ + __u8 salt_size; /* size of salt in bytes; 0 if none */ +#ifdef __KERNEL__ + __le32 sig_size; +#else + __le32 __reserved_0x04; /* must be 0 */ +#endif + __le64 data_size; /* size of file the Merkle tree is built over */ + __u8 root_hash[64]; /* Merkle tree root hash */ + __u8 salt[32]; /* salt prepended to each hashed block */ + __u8 __reserved[144]; /* must be 0's */ +#ifdef __KERNEL__ + __u8 signature[]; +#endif +}; + +/* + * Format in which fs-verity file digests are signed in built-in signatures. + * This is the same as 'struct fsverity_digest', except here some magic bytes + * are prepended to provide some context about what is being signed in case the + * same key is used for non-fsverity purposes, and here the fields have fixed + * endianness. + * + * This struct is specific to the built-in signature verification support, which + * is optional. fs-verity users may also verify signatures in userspace, in + * which case userspace is responsible for deciding on what bytes are signed. + * This struct may still be used, but it doesn't have to be. For example, + * userspace could instead use a string like "sha256:$digest_as_hex_string". + */ +struct fsverity_formatted_digest { + char magic[8]; /* must be "FSVerity" */ + __le16 digest_algorithm; + __le16 digest_size; + __u8 digest[]; +}; + #define FS_IOC_ENABLE_VERITY _IOW('f', 133, struct fsverity_enable_arg) #define FS_IOC_MEASURE_VERITY _IOWR('f', 134, struct fsverity_digest)