diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2019-05-07 21:28:04 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-05-07 21:28:04 -0700 |
commit | a9fbcd6728837268784439ad0b02ede2c024c516 (patch) | |
tree | 2a58af9a6f7573617ab482aea0998389d8b956af /fs/crypto | |
parent | 5abe37954e9a315c35c9490f78d55f307c3c636b (diff) | |
parent | 2c58d548f5706d085c4b009f6abb945220460632 (diff) |
Merge tag 'fscrypt_for_linus' of git://git.kernel.org/pub/scm/fs/fscrypt/fscrypt
Pull fscrypt updates from Ted Ts'o:
"Clean up fscrypt's dcache revalidation support, and other
miscellaneous cleanups"
* tag 'fscrypt_for_linus' of git://git.kernel.org/pub/scm/fs/fscrypt/fscrypt:
fscrypt: cache decrypted symlink target in ->i_link
vfs: use READ_ONCE() to access ->i_link
fscrypt: fix race where ->lookup() marks plaintext dentry as ciphertext
fscrypt: only set dentry_operations on ciphertext dentries
fs, fscrypt: clear DCACHE_ENCRYPTED_NAME when unaliasing directory
fscrypt: fix race allowing rename() and link() of ciphertext dentries
fscrypt: clean up and improve dentry revalidation
fscrypt: use READ_ONCE() to access ->i_crypt_info
fscrypt: remove WARN_ON_ONCE() when decryption fails
fscrypt: drop inode argument from fscrypt_get_ctx()
Diffstat (limited to 'fs/crypto')
-rw-r--r-- | fs/crypto/bio.c | 8 | ||||
-rw-r--r-- | fs/crypto/crypto.c | 74 | ||||
-rw-r--r-- | fs/crypto/fname.c | 5 | ||||
-rw-r--r-- | fs/crypto/hooks.c | 68 | ||||
-rw-r--r-- | fs/crypto/keyinfo.c | 25 | ||||
-rw-r--r-- | fs/crypto/policy.c | 6 |
6 files changed, 119 insertions, 67 deletions
diff --git a/fs/crypto/bio.c b/fs/crypto/bio.c index 8f3a8bc15d98..b46021ebde85 100644 --- a/fs/crypto/bio.c +++ b/fs/crypto/bio.c @@ -36,12 +36,10 @@ static void __fscrypt_decrypt_bio(struct bio *bio, bool done) int ret = fscrypt_decrypt_page(page->mapping->host, page, PAGE_SIZE, 0, page->index); - if (ret) { - WARN_ON_ONCE(1); + if (ret) SetPageError(page); - } else if (done) { + else if (done) SetPageUptodate(page); - } if (done) unlock_page(page); } @@ -103,7 +101,7 @@ int fscrypt_zeroout_range(const struct inode *inode, pgoff_t lblk, BUG_ON(inode->i_sb->s_blocksize != PAGE_SIZE); - ctx = fscrypt_get_ctx(inode, GFP_NOFS); + ctx = fscrypt_get_ctx(GFP_NOFS); if (IS_ERR(ctx)) return PTR_ERR(ctx); diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c index 4dc788e3bc96..68e2ca4c4af6 100644 --- a/fs/crypto/crypto.c +++ b/fs/crypto/crypto.c @@ -87,23 +87,17 @@ EXPORT_SYMBOL(fscrypt_release_ctx); /** * fscrypt_get_ctx() - Gets an encryption context - * @inode: The inode for which we are doing the crypto * @gfp_flags: The gfp flag for memory allocation * * Allocates and initializes an encryption context. * - * Return: An allocated and initialized encryption context on success; error - * value or NULL otherwise. + * Return: A new encryption context on success; an ERR_PTR() otherwise. */ -struct fscrypt_ctx *fscrypt_get_ctx(const struct inode *inode, gfp_t gfp_flags) +struct fscrypt_ctx *fscrypt_get_ctx(gfp_t gfp_flags) { - struct fscrypt_ctx *ctx = NULL; - struct fscrypt_info *ci = inode->i_crypt_info; + struct fscrypt_ctx *ctx; unsigned long flags; - if (ci == NULL) - return ERR_PTR(-ENOKEY); - /* * We first try getting the ctx from a free list because in * the common case the ctx will have an allocated and @@ -258,9 +252,9 @@ struct page *fscrypt_encrypt_page(const struct inode *inode, BUG_ON(!PageLocked(page)); - ctx = fscrypt_get_ctx(inode, gfp_flags); + ctx = fscrypt_get_ctx(gfp_flags); if (IS_ERR(ctx)) - return (struct page *)ctx; + return ERR_CAST(ctx); /* The encryption operation will require a bounce page. */ ciphertext_page = fscrypt_alloc_bounce_page(ctx, gfp_flags); @@ -313,45 +307,47 @@ int fscrypt_decrypt_page(const struct inode *inode, struct page *page, EXPORT_SYMBOL(fscrypt_decrypt_page); /* - * Validate dentries for encrypted directories to make sure we aren't - * potentially caching stale data after a key has been added or - * removed. + * Validate dentries in encrypted directories to make sure we aren't potentially + * caching stale dentries after a key has been added. */ static int fscrypt_d_revalidate(struct dentry *dentry, unsigned int flags) { struct dentry *dir; - int dir_has_key, cached_with_key; + int err; + int valid; + + /* + * Plaintext names are always valid, since fscrypt doesn't support + * reverting to ciphertext names without evicting the directory's inode + * -- which implies eviction of the dentries in the directory. + */ + if (!(dentry->d_flags & DCACHE_ENCRYPTED_NAME)) + return 1; + + /* + * Ciphertext name; valid if the directory's key is still unavailable. + * + * Although fscrypt forbids rename() on ciphertext names, we still must + * use dget_parent() here rather than use ->d_parent directly. That's + * because a corrupted fs image may contain directory hard links, which + * the VFS handles by moving the directory's dentry tree in the dcache + * each time ->lookup() finds the directory and it already has a dentry + * elsewhere. Thus ->d_parent can be changing, and we must safely grab + * a reference to some ->d_parent to prevent it from being freed. + */ if (flags & LOOKUP_RCU) return -ECHILD; dir = dget_parent(dentry); - if (!IS_ENCRYPTED(d_inode(dir))) { - dput(dir); - return 0; - } - - spin_lock(&dentry->d_lock); - cached_with_key = dentry->d_flags & DCACHE_ENCRYPTED_WITH_KEY; - spin_unlock(&dentry->d_lock); - dir_has_key = (d_inode(dir)->i_crypt_info != NULL); + err = fscrypt_get_encryption_info(d_inode(dir)); + valid = !fscrypt_has_encryption_key(d_inode(dir)); dput(dir); - /* - * If the dentry was cached without the key, and it is a - * negative dentry, it might be a valid name. We can't check - * if the key has since been made available due to locking - * reasons, so we fail the validation so ext4_lookup() can do - * this check. - * - * We also fail the validation if the dentry was created with - * the key present, but we no longer have the key, or vice versa. - */ - if ((!cached_with_key && d_is_negative(dentry)) || - (!cached_with_key && dir_has_key) || - (cached_with_key && !dir_has_key)) - return 0; - return 1; + if (err < 0) + return err; + + return valid; } const struct dentry_operations fscrypt_d_ops = { diff --git a/fs/crypto/fname.c b/fs/crypto/fname.c index 7ff40a73dbec..eccea3d8f923 100644 --- a/fs/crypto/fname.c +++ b/fs/crypto/fname.c @@ -269,7 +269,7 @@ int fscrypt_fname_disk_to_usr(struct inode *inode, if (iname->len < FS_CRYPTO_BLOCK_SIZE) return -EUCLEAN; - if (inode->i_crypt_info) + if (fscrypt_has_encryption_key(inode)) return fname_decrypt(inode, iname, oname); if (iname->len <= FSCRYPT_FNAME_MAX_UNDIGESTED_SIZE) { @@ -336,7 +336,7 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname, if (ret) return ret; - if (dir->i_crypt_info) { + if (fscrypt_has_encryption_key(dir)) { if (!fscrypt_fname_encrypted_size(dir, iname->len, dir->i_sb->s_cop->max_namelen, &fname->crypto_buf.len)) @@ -356,6 +356,7 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname, } if (!lookup) return -ENOKEY; + fname->is_ciphertext_name = true; /* * We don't have the key and we are doing a lookup; decode the diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c index 56debb1fcf5e..2dc22549d724 100644 --- a/fs/crypto/hooks.c +++ b/fs/crypto/hooks.c @@ -49,7 +49,8 @@ int fscrypt_file_open(struct inode *inode, struct file *filp) } EXPORT_SYMBOL_GPL(fscrypt_file_open); -int __fscrypt_prepare_link(struct inode *inode, struct inode *dir) +int __fscrypt_prepare_link(struct inode *inode, struct inode *dir, + struct dentry *dentry) { int err; @@ -57,6 +58,10 @@ int __fscrypt_prepare_link(struct inode *inode, struct inode *dir) if (err) return err; + /* ... in case we looked up ciphertext name before key was added */ + if (dentry->d_flags & DCACHE_ENCRYPTED_NAME) + return -ENOKEY; + if (!fscrypt_has_permitted_context(dir, inode)) return -EXDEV; @@ -78,6 +83,11 @@ int __fscrypt_prepare_rename(struct inode *old_dir, struct dentry *old_dentry, if (err) return err; + /* ... in case we looked up ciphertext name(s) before key was added */ + if ((old_dentry->d_flags | new_dentry->d_flags) & + DCACHE_ENCRYPTED_NAME) + return -ENOKEY; + if (old_dir != new_dir) { if (IS_ENCRYPTED(new_dir) && !fscrypt_has_permitted_context(new_dir, @@ -94,21 +104,21 @@ int __fscrypt_prepare_rename(struct inode *old_dir, struct dentry *old_dentry, } EXPORT_SYMBOL_GPL(__fscrypt_prepare_rename); -int __fscrypt_prepare_lookup(struct inode *dir, struct dentry *dentry) +int __fscrypt_prepare_lookup(struct inode *dir, struct dentry *dentry, + struct fscrypt_name *fname) { - int err = fscrypt_get_encryption_info(dir); + int err = fscrypt_setup_filename(dir, &dentry->d_name, 1, fname); - if (err) + if (err && err != -ENOENT) return err; - if (fscrypt_has_encryption_key(dir)) { + if (fname->is_ciphertext_name) { spin_lock(&dentry->d_lock); - dentry->d_flags |= DCACHE_ENCRYPTED_WITH_KEY; + dentry->d_flags |= DCACHE_ENCRYPTED_NAME; spin_unlock(&dentry->d_lock); + d_set_d_op(dentry, &fscrypt_d_ops); } - - d_set_d_op(dentry, &fscrypt_d_ops); - return 0; + return err; } EXPORT_SYMBOL_GPL(__fscrypt_prepare_lookup); @@ -179,11 +189,9 @@ int __fscrypt_encrypt_symlink(struct inode *inode, const char *target, sd->len = cpu_to_le16(ciphertext_len); err = fname_encrypt(inode, &iname, sd->encrypted_path, ciphertext_len); - if (err) { - if (!disk_link->name) - kfree(sd); - return err; - } + if (err) + goto err_free_sd; + /* * Null-terminating the ciphertext doesn't make sense, but we still * count the null terminator in the length, so we might as well @@ -191,9 +199,20 @@ int __fscrypt_encrypt_symlink(struct inode *inode, const char *target, */ sd->encrypted_path[ciphertext_len] = '\0'; + /* Cache the plaintext symlink target for later use by get_link() */ + err = -ENOMEM; + inode->i_link = kmemdup(target, len + 1, GFP_NOFS); + if (!inode->i_link) + goto err_free_sd; + if (!disk_link->name) disk_link->name = (unsigned char *)sd; return 0; + +err_free_sd: + if (!disk_link->name) + kfree(sd); + return err; } EXPORT_SYMBOL_GPL(__fscrypt_encrypt_symlink); @@ -202,7 +221,7 @@ EXPORT_SYMBOL_GPL(__fscrypt_encrypt_symlink); * @inode: the symlink inode * @caddr: the on-disk contents of the symlink * @max_size: size of @caddr buffer - * @done: if successful, will be set up to free the returned target + * @done: if successful, will be set up to free the returned target if needed * * If the symlink's encryption key is available, we decrypt its target. * Otherwise, we encode its target for presentation. @@ -217,12 +236,18 @@ const char *fscrypt_get_symlink(struct inode *inode, const void *caddr, { const struct fscrypt_symlink_data *sd; struct fscrypt_str cstr, pstr; + bool has_key; int err; /* This is for encrypted symlinks only */ if (WARN_ON(!IS_ENCRYPTED(inode))) return ERR_PTR(-EINVAL); + /* If the decrypted target is already cached, just return it. */ + pstr.name = READ_ONCE(inode->i_link); + if (pstr.name) + return pstr.name; + /* * Try to set up the symlink's encryption key, but we can continue * regardless of whether the key is available or not. @@ -230,6 +255,7 @@ const char *fscrypt_get_symlink(struct inode *inode, const void *caddr, err = fscrypt_get_encryption_info(inode); if (err) return ERR_PTR(err); + has_key = fscrypt_has_encryption_key(inode); /* * For historical reasons, encrypted symlink targets are prefixed with @@ -261,7 +287,17 @@ const char *fscrypt_get_symlink(struct inode *inode, const void *caddr, goto err_kfree; pstr.name[pstr.len] = '\0'; - set_delayed_call(done, kfree_link, pstr.name); + + /* + * Cache decrypted symlink targets in i_link for later use. Don't cache + * symlink targets encoded without the key, since those become outdated + * once the key is added. This pairs with the READ_ONCE() above and in + * the VFS path lookup code. + */ + if (!has_key || + cmpxchg_release(&inode->i_link, NULL, pstr.name) != NULL) + set_delayed_call(done, kfree_link, pstr.name); + return pstr.name; err_kfree: diff --git a/fs/crypto/keyinfo.c b/fs/crypto/keyinfo.c index 2cb4956f8511..dcd91a3fbe49 100644 --- a/fs/crypto/keyinfo.c +++ b/fs/crypto/keyinfo.c @@ -508,7 +508,7 @@ int fscrypt_get_encryption_info(struct inode *inode) u8 *raw_key = NULL; int res; - if (inode->i_crypt_info) + if (fscrypt_has_encryption_key(inode)) return 0; res = fscrypt_initialize(inode->i_sb->s_cop->flags); @@ -572,7 +572,7 @@ int fscrypt_get_encryption_info(struct inode *inode) if (res) goto out; - if (cmpxchg(&inode->i_crypt_info, NULL, crypt_info) == NULL) + if (cmpxchg_release(&inode->i_crypt_info, NULL, crypt_info) == NULL) crypt_info = NULL; out: if (res == -ENOKEY) @@ -583,9 +583,30 @@ out: } EXPORT_SYMBOL(fscrypt_get_encryption_info); +/** + * fscrypt_put_encryption_info - free most of an inode's fscrypt data + * + * Free the inode's fscrypt_info. Filesystems must call this when the inode is + * being evicted. An RCU grace period need not have elapsed yet. + */ void fscrypt_put_encryption_info(struct inode *inode) { put_crypt_info(inode->i_crypt_info); inode->i_crypt_info = NULL; } EXPORT_SYMBOL(fscrypt_put_encryption_info); + +/** + * fscrypt_free_inode - free an inode's fscrypt data requiring RCU delay + * + * Free the inode's cached decrypted symlink target, if any. Filesystems must + * call this after an RCU grace period, just before they free the inode. + */ +void fscrypt_free_inode(struct inode *inode) +{ + if (IS_ENCRYPTED(inode) && S_ISLNK(inode->i_mode)) { + kfree(inode->i_link); + inode->i_link = NULL; + } +} +EXPORT_SYMBOL(fscrypt_free_inode); diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c index bd7eaf9b3f00..d536889ac31b 100644 --- a/fs/crypto/policy.c +++ b/fs/crypto/policy.c @@ -194,8 +194,8 @@ int fscrypt_has_permitted_context(struct inode *parent, struct inode *child) res = fscrypt_get_encryption_info(child); if (res) return 0; - parent_ci = parent->i_crypt_info; - child_ci = child->i_crypt_info; + parent_ci = READ_ONCE(parent->i_crypt_info); + child_ci = READ_ONCE(child->i_crypt_info); if (parent_ci && child_ci) { return memcmp(parent_ci->ci_master_key_descriptor, @@ -246,7 +246,7 @@ int fscrypt_inherit_context(struct inode *parent, struct inode *child, if (res < 0) return res; - ci = parent->i_crypt_info; + ci = READ_ONCE(parent->i_crypt_info); if (ci == NULL) return -ENOKEY; |