Update crypto-fix-validate-when-key-add-remove
[ext4-patch-queue.git] / crypto-fix-validate-when-key-add-remove
blobd309dedfc10a4285ffda604eaef266c650af21a7
1 ext4 crypto: revalidate dentry after adding or removing the key
3 Add a validation check for dentries for encrypted directory to make
4 sure we're not caching stale data after a key has been added or removed.
6 Also check to make sure that status of the encryption key is updated
7 when readdir(2) is executed.
9 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
11 ---
12  fs/ext4/crypto.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
13  fs/ext4/dir.c    |  6 ++++++
14  fs/ext4/ext4.h   |  1 +
15  fs/ext4/namei.c  | 18 ++++++++++++++++++
16  4 files changed, 81 insertions(+)
18 diff --git a/fs/ext4/crypto.c b/fs/ext4/crypto.c
19 index c802120..38f7562 100644
20 --- a/fs/ext4/crypto.c
21 +++ b/fs/ext4/crypto.c
22 @@ -467,3 +467,59 @@ uint32_t ext4_validate_encryption_key_size(uint32_t mode, uint32_t size)
23                 return size;
24         return 0;
25  }
27 +/*
28 + * Validate dentries for encrypted directories to make sure we aren't
29 + * potentially caching stale data after a key has been added or
30 + * removed.
31 + */
32 +static int ext4_d_revalidate(struct dentry *dentry, unsigned int flags)
34 +       struct inode *dir = d_inode(dentry->d_parent);
35 +       struct ext4_crypt_info *ci = EXT4_I(dir)->i_crypt_info;
36 +       int dir_has_key, cached_with_key;
38 +       if (!ext4_encrypted_inode(dir))
39 +               return 0;
41 +       if (ci && ci->ci_keyring_key &&
42 +           (ci->ci_keyring_key->flags & ((1 << KEY_FLAG_INVALIDATED) |
43 +                                         (1 << KEY_FLAG_REVOKED) |
44 +                                         (1 << KEY_FLAG_DEAD))))
45 +               ci = NULL;
47 +       /* this should eventually be an flag in d_flags */
48 +       cached_with_key = dentry->d_fsdata != NULL;
49 +       dir_has_key = (ci != NULL);
51 +       /*
52 +        * If the dentry was cached without the key, and it is a
53 +        * negative dentry, it might be a valid name.  We can't check
54 +        * if the key has since been made available due to locking
55 +        * reasons, so we fail the validation so ext4_lookup() can do
56 +        * this check.
57 +        *
58 +        * We also fail the validation if the dentry was created with
59 +        * the key present, but we no longer have the key, or vice versa.
60 +        */
61 +       if ((!cached_with_key && d_is_negative(dentry)) ||
62 +           (!cached_with_key && dir_has_key) ||
63 +           (cached_with_key && !dir_has_key)) {
64 +#if 0                          /* Revalidation debug */
65 +               char buf[80];
66 +               char *cp = simple_dname(dentry, buf, sizeof(buf));
68 +               if (IS_ERR(cp))
69 +                       cp = (char *) "???";
70 +               pr_err("revalidate: %s %p %d %d %d\n", cp, dentry->d_fsdata,
71 +                      cached_with_key, d_is_negative(dentry),
72 +                      dir_has_key);
73 +#endif
74 +               return 0;
75 +       }
76 +       return 1;
79 +const struct dentry_operations ext4_encrypted_d_ops = {
80 +       .d_revalidate = ext4_d_revalidate,
81 +};
82 diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c
83 index 1d1bca7..6d17f31 100644
84 --- a/fs/ext4/dir.c
85 +++ b/fs/ext4/dir.c
86 @@ -111,6 +111,12 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx)
87         int dir_has_error = 0;
88         struct ext4_str fname_crypto_str = {.name = NULL, .len = 0};
90 +       if (ext4_encrypted_inode(inode)) {
91 +               err = ext4_get_encryption_info(inode);
92 +               if (err && err != -ENOKEY)
93 +                       return err;
94 +       }
96         if (is_dx_dir(inode)) {
97                 err = ext4_dx_readdir(file, ctx);
98                 if (err != ERR_BAD_DX_DIR) {
99 diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
100 index 0662b28..157b458a 100644
101 --- a/fs/ext4/ext4.h
102 +++ b/fs/ext4/ext4.h
103 @@ -2302,6 +2302,7 @@ struct page *ext4_encrypt(struct inode *inode,
104  int ext4_decrypt(struct page *page);
105  int ext4_encrypted_zeroout(struct inode *inode, ext4_lblk_t lblk,
106                            ext4_fsblk_t pblk, ext4_lblk_t len);
107 +extern const struct dentry_operations ext4_encrypted_d_ops;
109  #ifdef CONFIG_EXT4_FS_ENCRYPTION
110  int ext4_init_crypto(void);
111 diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
112 index 06574dd..5de8483 100644
113 --- a/fs/ext4/namei.c
114 +++ b/fs/ext4/namei.c
115 @@ -1558,6 +1558,24 @@ static struct dentry *ext4_lookup(struct inode *dir, struct dentry *dentry, unsi
116         struct ext4_dir_entry_2 *de;
117         struct buffer_head *bh;
119 +       if (ext4_encrypted_inode(dir)) {
120 +               int res = ext4_get_encryption_info(dir);
122 +               /*
123 +                * This should be a properly defined flag for
124 +                * dentry->d_flags when we uplift this to the VFS.
125 +                * d_fsdata is set to (void *) 1 if if the dentry is
126 +                * created while the directory was encrypted and we
127 +                * don't have access to the key.
128 +                */
129 +              dentry->d_fsdata = NULL;
130 +              if (ext4_encryption_info(dir))
131 +                      dentry->d_fsdata = (void *) 1;
132 +              d_set_d_op(dentry, &ext4_encrypted_d_ops);
133 +              if (res && res != -ENOKEY)
134 +                      return ERR_PTR(res);
135 +       }
137         if (dentry->d_name.len > EXT4_NAME_LEN)
138                 return ERR_PTR(-ENAMETOOLONG);