Whamcloud - gitweb
LU-15848 ldiskfs: escape encrypted file names
[fs/lustre-release.git] / ldiskfs / kernel_patches / patches / rhel8 / ext4-filename-encode.patch
1 diff -wur /dev/null b/fs/ext4/critical_encode.h
2 --- /dev/null
3 +++ b/fs/ext4/critical_encode.h
4 @@ -0,0 +1,158 @@
5 +/*
6 + *  critical_encode.h
7 + *
8 + *  Copyright (c) 2022 Whamcloud
9 + */
10 +
11 +#ifndef _CRITICAL_ENCODE_H
12 +#define _CRITICAL_ENCODE_H
13 +
14 +#include <linux/ctype.h>
15 +
16 +/* Encoding/decoding routines inspired from yEnc principles.
17 + * We just take care of a few critical characters:
18 + * NULL, LF, CR, /, DEL and =.
19 + * If such a char is found, it is replaced with '=' followed by
20 + * the char value + 64.
21 + * All other chars are left untouched.
22 + * Efficiency of this encoding depends on the occurences of the
23 + * critical chars, but statistically on binary data it can be much higher
24 + * than base64 for instance.
25 + */
26 +static inline int critical_encode(const u8 *src, int len, char *dst)
27 +{
28 +       u8 *p = (u8 *)src, *q = dst;
29 +
30 +       while (p - src < len) {
31 +               /* escape NULL, LF, CR, /, DEL and = */
32 +               if (unlikely(*p == 0x0 || *p == 0xA || *p == 0xD ||
33 +                            *p == '/' || *p == 0x7F || *p == '=')) {
34 +                       *(q++) = '=';
35 +                       *(q++) = *(p++) + 64;
36 +               } else {
37 +                       *(q++) = *(p++);
38 +               }
39 +       }
40 +
41 +       return (char *)q - dst;
42 +}
43 +
44 +/* returns the number of chars encoding would produce */
45 +static inline int critical_chars(const u8 *src, int len)
46 +{
47 +       u8 *p = (u8 *)src;
48 +       int newlen = len;
49 +
50 +       while (p - src < len) {
51 +               /* NULL, LF, CR, /, DEL and = cost an additional '=' */
52 +               if (unlikely(*p == 0x0 || *p == 0xA || *p == 0xD ||
53 +                            *p == '/' || *p == 0x7F || *p == '='))
54 +                       newlen++;
55 +               p++;
56 +       }
57 +
58 +       return newlen;
59 +}
60 +
61 +/* decoding routine - returns the number of chars in output */
62 +static inline int critical_decode(const u8 *src, int len, char *dst)
63 +{
64 +       u8 *p = (u8 *)src, *q = dst;
65 +
66 +       while (p - src < len) {
67 +               if (unlikely(*p == '=')) {
68 +                       *(q++) = *(++p) - 64;
69 +                       p++;
70 +               } else {
71 +                       *(q++) = *(p++);
72 +               }
73 +       }
74 +
75 +       return (char *)q - dst;
76 +}
77 +
78 +#define fscrypt_get_encryption_info(inode) \
79 +       (unlikely(!IS_LUSTRE_MOUNT(inode->i_sb)) ? 0 : -EOPNOTSUPP)
80 +
81 +static inline int ext4_prepare_lookup(struct inode *dir,
82 +                                         struct dentry *dentry,
83 +                                         unsigned int flags)
84 +{
85 +       if (unlikely(!IS_LUSTRE_MOUNT(dir->i_sb)))
86 +               return 0;
87 +        return fscrypt_prepare_lookup(dir, dentry, flags);
88 +}
89 +
90 +static inline int ext4_fname_alloc_buffer(const struct inode *inode,
91 +                                            u32 max_encrypted_len,
92 +                                            struct fscrypt_str *crypto_str)
93 +{
94 +       crypto_str->name = kmalloc(max_encrypted_len + 1, GFP_NOFS);
95 +       if (!crypto_str->name)
96 +               return -ENOMEM;
97 +       crypto_str->len = max_encrypted_len;
98 +       return 0;
99 +}
100 +
101 +static inline void ext4_fname_free_buffer(struct fscrypt_str *crypto_str)
102 +{
103 +       if (!crypto_str)
104 +               return;
105 +       kfree(crypto_str->name);
106 +       crypto_str->name = NULL;
107 +}
108 +
109 +static inline int ext4_fname_disk_to_usr(struct inode *inode,
110 +                                           u32 hash, u32 minor_hash,
111 +                                           const struct fscrypt_str *iname,
112 +                                           struct fscrypt_str *oname)
113 +{
114 +       int presented_len;
115 +
116 +       presented_len = critical_encode(iname->name, iname->len, oname->name);
117 +       if (presented_len > NAME_MAX) {
118 +               /* truncate at NAME_MAX,
119 +                * or NAME_MAX-1 if name ends with '=' to avoid decoding issue
120 +                */
121 +               presented_len = NAME_MAX;
122 +               if (oname->name[presented_len - 1] == '=')
123 +                       presented_len--;
124 +               oname->len = presented_len;
125 +       }
126 +       oname->name[presented_len] = '\0';
127 +
128 +       return 0;
129 +}
130 +
131 +static inline int ext4_setup_filename(struct inode *dir,
132 +                                        const struct qstr *iname,
133 +                                        int lookup,
134 +                                        struct ext4_filename *fname)
135 +{
136 +       fname->usr_fname = iname;
137 +
138 +       if (lookup && IS_ENCRYPTED(dir) &&
139 +           unlikely(!IS_LUSTRE_MOUNT(dir->i_sb) &&
140 +                    strnchr(iname->name, iname->len, '='))) {
141 +               /* Only proceed to critical decode if
142 +                * iname contains escape char '='.
143 +                */
144 +               int len = iname->len;
145 +               char *buf;
146 +
147 +               buf = kmalloc(len, GFP_NOFS);
148 +               if (!buf)
149 +                       return -ENOMEM;
150 +
151 +               len = critical_decode(iname->name, len, buf);
152 +               fname->disk_name.name = (unsigned char *)buf;
153 +               fname->disk_name.len = len;
154 +               return 0;
155 +       }
156 +
157 +       fname->disk_name.name = (unsigned char *) iname->name;
158 +       fname->disk_name.len = iname->len;
159 +       return 0;
160 +}
161 +
162 +#endif /* _CRITICAL_ENCODE_H */
163 diff -wur a/fs/ext4/dir.c b/fs/ext4/dir.c
164 --- a/fs/ext4/dir.c
165 +++ b/fs/ext4/dir.c
166 @@ -28,6 +28,7 @@
167  #include <linux/iversion.h>
168  #include "ext4.h"
169  #include "xattr.h"
170 +#include "critical_encode.h"
171  
172  static int ext4_dx_readdir(struct file *, struct dir_context *);
173  
174 @@ -144,7 +145,8 @@ static int ext4_readdir(struct file *
175                         return err;
176         }
177  
178 -       if (IS_ENCRYPTED(inode)) {
179 +       /* disable decryption of filename, present only escaped name */
180 +       if (0 && IS_ENCRYPTED(inode)) {
181                 err = fscrypt_fname_alloc_buffer(inode, EXT4_NAME_LEN, &fstr);
182                 if (err < 0)
183                         return err;
184 @@ -258,22 +259,33 @@ static int ext4_readdir(struct file *
185                                             get_dtype(sb, de->file_type)))
186                                                 goto done;
187                                 } else {
188 -                                       int save_len = fstr.len;
189                                         struct fscrypt_str de_name =
190                                                         FSTR_INIT(de->name,
191                                                                 de->name_len);
192 +                                       int presented_len;
193  
194                                         /* Directory is encrypted */
195 -                                       err = fscrypt_fname_disk_to_usr(inode,
196 +                                       presented_len = critical_chars(de->name,
197 +                                                                 de->name_len);
198 +                                       err = ext4_fname_alloc_buffer(inode,
199 +                                                                 presented_len,
200 +                                                                 &fstr);
201 +                                       if (err)
202 +                                               goto errout;
203 +
204 +                                       err = ext4_fname_disk_to_usr(inode,
205                                                 0, 0, &de_name, &fstr);
206                                         de_name = fstr;
207 -                                       fstr.len = save_len;
208 -                                       if (err)
209 +                                       if (err) {
210 +                                               ext4_fname_free_buffer(&fstr);
211                                                 goto errout;
212 -                                       if (!dir_emit(ctx,
213 +                                       }
214 +                                       err = dir_emit(ctx,
215                                             de_name.name, de_name.len,
216                                             le32_to_cpu(de->inode),
217 -                                           get_dtype(sb, de->file_type)))
218 +                                                 get_dtype(sb, de->file_type));
219 +                                       ext4_fname_free_buffer(&fstr);
220 +                                       if (!err)
221                                                 goto done;
222                                 }
223                         }
224 diff -wur a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
225 --- a/fs/ext4/ialloc.c
226 +++ b/fs/ext4/ialloc.c
227 @@ -30,6 +30,7 @@
228  #include "ext4_jbd2.h"
229  #include "xattr.h"
230  #include "acl.h"
231 +#include "critical_encode.h"
232  
233  #include <trace/events/ext4.h>
234  
235 diff -wur a/fs/ext4/namei.c b/fs/ext4/namei.c
236 --- a/fs/ext4/namei.c
237 +++ b/fs/ext4/namei.c
238 @@ -40,6 +40,7 @@
239  
240  #include "xattr.h"
241  #include "acl.h"
242 +#include "critical_encode.h"
243  
244  #include <trace/events/ext4.h>
245  /*
246 @@ -1368,22 +1369,31 @@ static int htree_dirblock_to_tree(struct
247                                    hinfo->hash, hinfo->minor_hash, de,
248                                    &tmp_str);
249                 } else {
250 -                       int save_len = fname_crypto_str.len;
251                         struct fscrypt_str de_name = FSTR_INIT(de->name,
252                                                                 de->name_len);
253 +                       int presented_len;
254  
255                         /* Directory is encrypted */
256 -                       err = fscrypt_fname_disk_to_usr(dir, hinfo->hash,
257 +                       presented_len = critical_chars(de->name, de->name_len);
258 +                       err = ext4_fname_alloc_buffer(dir, presented_len,
259 +                                                        &fname_crypto_str);
260 +                       if (err) {
261 +                               count = err;
262 +                               goto errout;
263 +                       }
264 +
265 +                       err = ext4_fname_disk_to_usr(dir, hinfo->hash,
266                                         hinfo->minor_hash, &de_name,
267                                         &fname_crypto_str);
268                         if (err) {
269 +                               ext4_fname_free_buffer(&fname_crypto_str);
270                                 count = err;
271                                 goto errout;
272                         }
273                         err = ext4_htree_store_dirent(dir_file,
274                                    hinfo->hash, hinfo->minor_hash, de,
275                                         &fname_crypto_str);
276 -                       fname_crypto_str.len = save_len;
277 +                       ext4_fname_free_buffer(&fname_crypto_str);
278                 }
279                 if (err != 0) {
280                         count = err;
281 @@ -1614,7 +1614,7 @@ static void dx_insert_block(struct dx_fr
282   * Return: %true if the directory entry matches, otherwise %false.
283   */
284  static inline bool ext4_match(const struct ext4_filename *fname,
285 -                             const struct ext4_dir_entry_2 *de)
286 +                             const struct ext4_dir_entry_2 *de, int denamelen)
287  {
288         struct fscrypt_name f;
289  
290 @@ -1626,7 +1626,7 @@ static inline bool ext4_match(const s
291  #ifdef CONFIG_EXT4_FS_ENCRYPTION
292         f.crypto_buf = fname->crypto_buf;
293  #endif
294 -       return fscrypt_match_name(&f, de->name, de->name_len);
295 +       return fscrypt_match_name(&f, de->name, denamelen);
296  }
297  
298  /*
299 @@ -1637,16 +1637,30 @@ int ext4_search_dir(struct buffer_hea
300                     unsigned int offset, struct ext4_dir_entry_2 **res_dir)
301  {
302         struct ext4_dir_entry_2 * de;
303 +       bool probablytrunc;
304         char * dlimit;
305 -       int de_len;
306 +       int de_len, denamelen;
307  
308         de = (struct ext4_dir_entry_2 *)search_buf;
309         dlimit = search_buf + buf_size;
310 +       /* fname is probably truncated if it is the decoded representation of
311 +        * an encrypted filename not aligned on a 32-byte boundary
312 +        */
313 +       probablytrunc = !IS_LUSTRE_MOUNT(dir->i_sb) && IS_ENCRYPTED(dir) &&
314 +               fname->disk_name.len & 31;
315         while ((char *) de < dlimit) {
316                 /* this code is executed quadratically often */
317                 /* do minimal checking `by hand' */
318 +               denamelen = de->name_len;
319 +               if (unlikely(probablytrunc) &&
320 +                   de->name_len > fname->disk_name.len)
321 +                       /* Adjust name len to look for a partial match.
322 +                        * Since it is binary encrypted names, there
323 +                        * should not be any collision between names.
324 +                        */
325 +                       denamelen = fname->disk_name.len;
326                 if ((char *) de + de->name_len <= dlimit &&
327 -                   ext4_match(fname, de)) {
328 +                   ext4_match(fname, de, denamelen)) {
329                         /* found a match - just to be sure, do
330                          * a full check */
331                         if (ext4_check_dir_entry(dir, NULL, de, bh, bh->b_data,
332 @@ -1707,7 +1717,7 @@ struct buffer_head *__ext4_find_entry
333         if (namelen > EXT4_NAME_LEN)
334                 return NULL;
335  
336 -       retval = ext4_fname_setup_filename(dir, d_name, 1, &fname);
337 +       retval = ext4_setup_filename(dir, d_name, 1, &fname);
338         if (retval == -ENOENT)
339                 return NULL;
340         if (retval)
341 @@ -1834,7 +1844,8 @@ cleanup_and_exit:
342         /* Clean up the read-ahead blocks */
343         for (; ra_ptr < ra_max; ra_ptr++)
344                 brelse(bh_use[ra_ptr]);
345 -       ext4_fname_free_filename(&fname);
346 +       if (fname.disk_name.name != d_name->name)
347 +               kfree(fname.disk_name.name);
348         return ret;
349  }
350  EXPORT_SYMBOL(__ext4_find_entry);
351 @@ -1900,7 +1911,7 @@ static struct dentry *ext4_lookup(str
352         struct buffer_head *bh;
353         int err;
354  
355 -       err = fscrypt_prepare_lookup(dir, dentry, flags);
356 +       err = ext4_prepare_lookup(dir, dentry, flags);
357         if (err)
358                 return ERR_PTR(err);
359  
360 @@ -2206,7 +2221,7 @@ int ext4_find_dest_de(struct inode *d
361                 if (ext4_check_dir_entry(dir, NULL, de, bh,
362                                          buf, buf_size, offset))
363                         return -EFSCORRUPTED;
364 -               if (ext4_match(fname, de))
365 +               if (ext4_match(fname, de, de->name_len))
366                         return -EEXIST;
367                 nlen = EXT4_DIR_ENTRY_LEN(de);
368                 rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);