ext4 crypto: revalidate dentry after adding or removing the key

Add a validation check for dentries for encrypted directory to make
sure we're not caching stale data after a key has been added or removed.

Also check to make sure that status of the encryption key is updated
when readdir(2) is executed.

Signed-off-by: Theodore Ts'o <tytso@mit.edu>
This commit is contained in:
Theodore Ts'o 2016-02-07 19:35:05 -05:00
parent 36f90b0a2d
commit 28b4c26396
4 changed files with 81 additions and 0 deletions

View file

@ -467,3 +467,59 @@ uint32_t ext4_validate_encryption_key_size(uint32_t mode, uint32_t size)
return size;
return 0;
}
/*
* Validate dentries for encrypted directories to make sure we aren't
* potentially caching stale data after a key has been added or
* removed.
*/
static int ext4_d_revalidate(struct dentry *dentry, unsigned int flags)
{
struct inode *dir = d_inode(dentry->d_parent);
struct ext4_crypt_info *ci = EXT4_I(dir)->i_crypt_info;
int dir_has_key, cached_with_key;
if (!ext4_encrypted_inode(dir))
return 0;
if (ci && ci->ci_keyring_key &&
(ci->ci_keyring_key->flags & ((1 << KEY_FLAG_INVALIDATED) |
(1 << KEY_FLAG_REVOKED) |
(1 << KEY_FLAG_DEAD))))
ci = NULL;
/* this should eventually be an flag in d_flags */
cached_with_key = dentry->d_fsdata != NULL;
dir_has_key = (ci != NULL);
/*
* 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)) {
#if 0 /* Revalidation debug */
char buf[80];
char *cp = simple_dname(dentry, buf, sizeof(buf));
if (IS_ERR(cp))
cp = (char *) "???";
pr_err("revalidate: %s %p %d %d %d\n", cp, dentry->d_fsdata,
cached_with_key, d_is_negative(dentry),
dir_has_key);
#endif
return 0;
}
return 1;
}
const struct dentry_operations ext4_encrypted_d_ops = {
.d_revalidate = ext4_d_revalidate,
};

View file

@ -111,6 +111,12 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx)
int dir_has_error = 0;
struct ext4_str fname_crypto_str = {.name = NULL, .len = 0};
if (ext4_encrypted_inode(inode)) {
err = ext4_get_encryption_info(inode);
if (err && err != -ENOKEY)
return err;
}
if (is_dx_dir(inode)) {
err = ext4_dx_readdir(file, ctx);
if (err != ERR_BAD_DX_DIR) {

View file

@ -2302,6 +2302,7 @@ struct page *ext4_encrypt(struct inode *inode,
int ext4_decrypt(struct page *page);
int ext4_encrypted_zeroout(struct inode *inode, ext4_lblk_t lblk,
ext4_fsblk_t pblk, ext4_lblk_t len);
extern const struct dentry_operations ext4_encrypted_d_ops;
#ifdef CONFIG_EXT4_FS_ENCRYPTION
int ext4_init_crypto(void);

View file

@ -1558,6 +1558,24 @@ static struct dentry *ext4_lookup(struct inode *dir, struct dentry *dentry, unsi
struct ext4_dir_entry_2 *de;
struct buffer_head *bh;
if (ext4_encrypted_inode(dir)) {
int res = ext4_get_encryption_info(dir);
/*
* This should be a properly defined flag for
* dentry->d_flags when we uplift this to the VFS.
* d_fsdata is set to (void *) 1 if if the dentry is
* created while the directory was encrypted and we
* don't have access to the key.
*/
dentry->d_fsdata = NULL;
if (ext4_encryption_info(dir))
dentry->d_fsdata = (void *) 1;
d_set_d_op(dentry, &ext4_encrypted_d_ops);
if (res && res != -ENOKEY)
return ERR_PTR(res);
}
if (dentry->d_name.len > EXT4_NAME_LEN)
return ERR_PTR(-ENAMETOOLONG);