diff options
| author | Theodore Ts'o <tytso@mit.edu> | 2016-02-07 19:35:05 -0500 |
|---|---|---|
| committer | Mister Oyster <oysterized@gmail.com> | 2017-05-29 03:52:06 +0200 |
| commit | 1487abde26ae07c507d95f4a59021b2f647e64f1 (patch) | |
| tree | a589facb5e481b13cf9abf2450312d18a69364b1 | |
| parent | 72e896d8d5698abc18c4e22995edabc242a7fa62 (diff) | |
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>
Signed-off-by: Theodore Ts'o <tytso@google.com>
Change-Id: Ic7a90d79d9447272fc512ae2abbd299523de02b8
| -rw-r--r-- | fs/ext4/crypto.c | 56 | ||||
| -rw-r--r-- | fs/ext4/dir.c | 6 | ||||
| -rw-r--r-- | fs/ext4/ext4.h | 1 | ||||
| -rw-r--r-- | fs/ext4/namei.c | 18 | ||||
| -rw-r--r-- | include/linux/dcache.h | 17 |
5 files changed, 98 insertions, 0 deletions
diff --git a/fs/ext4/crypto.c b/fs/ext4/crypto.c index f4631c1d0..934394c44 100644 --- a/fs/ext4/crypto.c +++ b/fs/ext4/crypto.c @@ -468,3 +468,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, +}; diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c index f5a0f088c..c3230fbdd 100644 --- a/fs/ext4/dir.c +++ b/fs/ext4/dir.c @@ -115,6 +115,12 @@ static int ext4_readdir2(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) { diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index ca2a563f1..bed75a1ad 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -2067,6 +2067,7 @@ struct page *ext4_encrypt(struct inode *inode, struct page *plaintext_page); int ext4_decrypt(struct page *page); int ext4_encrypted_zeroout(struct inode *inode, struct ext4_extent *ex); +extern const struct dentry_operations ext4_encrypted_d_ops; #ifdef CONFIG_EXT4_FS_ENCRYPTION int ext4_init_crypto(void); diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index f005ee9c0..bea7590fe 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -1547,6 +1547,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); diff --git a/include/linux/dcache.h b/include/linux/dcache.h index f2c042f1c..15b03546d 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -420,6 +420,23 @@ static inline bool d_is_su(const struct dentry *dentry) !memcmp(dentry->d_name.name, "su", 2); } +/** + * d_inode - Get the actual inode of this dentry + * @dentry: The dentry to query + * + * This is the helper normal filesystems should use to get at their own inodes + * in their own dentries and ignore the layering superimposed upon them. + */ +static inline struct inode *d_inode(const struct dentry *dentry) +{ + return dentry->d_inode; +} + +static inline bool d_is_negative(const struct dentry *dentry) +{ + return (dentry->d_inode == NULL); +} + extern int sysctl_vfs_cache_pressure; #endif /* __LINUX_DCACHE_H */ |
