Whamcloud - gitweb
LU-15848 ldiskfs: escape encrypted file names
[fs/lustre-release.git] / ldiskfs / kernel_patches / patches / rhel7.9 / ext4-filename-encode.patch
1 diff -wur a/fs/ext4/dir.c b/fs/ext4/dir.c
2 --- a/fs/ext4/dir.c
3 +++ b/fs/ext4/dir.c
4 @@ -28,6 +28,7 @@
5  #include <linux/rbtree.h>
6  #include "ext4.h"
7  #include "xattr.h"
8 +#include "critical_encode.h"
9  
10  static int ext4_dx_readdir(struct file *filp,
11                            void *dirent, filldir_t filldir);
12 @@ -243,12 +244,41 @@ revalidate:
13                                  * during the copy operation.
14                                  */
15                                 u64 version = filp->f_version;
16 +                               int presented_len = de->name_len;
17 +                               char *buf = de->name;
18  
19 -                               error = filldir(dirent, de->name,
20 -                                               de->name_len,
21 +                               if (unlikely(!IS_LUSTRE_MOUNT(inode->i_sb)) &&
22 +                                   EXT4_I(inode)->i_flags & EXT4_ENCRYPT_FL)
23 +                                       presented_len = critical_chars(de->name,
24 +                                                                 de->name_len);
25 +                               if (unlikely(de->name_len != presented_len)) {
26 +                                       buf = kmalloc(presented_len + 1,
27 +                                                     GFP_NOFS);
28 +                                       if (!buf) {
29 +                                               error = -ENOMEM;
30 +                                               break;
31 +                                       }
32 +                                       critical_encode(de->name,
33 +                                                       de->name_len, buf);
34 +                                       if (presented_len > NAME_MAX) {
35 +                                               /* truncate at NAME_MAX, or
36 +                                                * NAME_MAX-1 if name ends with
37 +                                                * '=' to avoid decoding issue
38 +                                                */
39 +                                               presented_len = NAME_MAX;
40 +                                               if (buf[NAME_MAX - 1] == '=')
41 +                                                       presented_len--;
42 +                                       }
43 +                                       buf[presented_len] = '\0';
44 +                               }
45 +
46 +                               error = filldir(dirent, buf,
47 +                                               presented_len,
48                                                 filp->f_pos,
49                                                 le32_to_cpu(de->inode),
50                                                 get_dtype(sb, de->file_type));
51 +                               if (unlikely(de->name_len != presented_len))
52 +                                       kfree(buf);
53                                 if (error)
54                                         break;
55                                 if (version != filp->f_version)
56 @@ -516,10 +542,38 @@ static int call_filldir(struct file *fil
57         }
58         curr_pos = hash2pos(filp, fname->hash, fname->minor_hash);
59         while (fname) {
60 -               error = filldir(dirent, fname->name,
61 -                               fname->name_len, curr_pos,
62 +               int presented_len = fname->name_len;
63 +               char *buf = fname->name;
64 +
65 +               if (unlikely(!IS_LUSTRE_MOUNT(inode->i_sb)) &&
66 +                   EXT4_I(inode)->i_flags & EXT4_ENCRYPT_FL)
67 +                       presented_len = critical_chars(fname->name,
68 +                                                      fname->name_len);
69 +               if (unlikely(fname->name_len != presented_len)) {
70 +                       buf = kmalloc(presented_len + 1, GFP_NOFS);
71 +                       if (!buf) {
72 +                               filp->f_pos = curr_pos;
73 +                               info->extra_fname = fname;
74 +                               return -ENOMEM;
75 +                       }
76 +                       critical_encode(fname->name,
77 +                                       fname->name_len, buf);
78 +                       if (presented_len > NAME_MAX) {
79 +                               /* truncate at NAME_MAX, or NAME_MAX-1 if
80 +                                * name ends with '=' to avoid decoding issue
81 +                                */
82 +                               presented_len = NAME_MAX;
83 +                               if (buf[presented_len - 1] == '=')
84 +                                       presented_len--;
85 +                       }
86 +                       buf[presented_len] = '\0';
87 +               }
88 +               error = filldir(dirent, buf,
89 +                               presented_len, curr_pos,
90                                 fname->inode,
91                                 get_dtype(sb, fname->file_type));
92 +               if (unlikely(fname->name_len != presented_len))
93 +                       kfree(buf);
94                 if (error) {
95                         filp->f_pos = curr_pos;
96                         info->extra_fname = fname;
97 diff -wur /dev/null b/fs/ext4/critical_encode.h
98 --- /dev/null
99 +++ b/fs/ext4/critical_encode.h
100 @@ -0,0 +1,74 @@
101 +/*
102 + *  critical_encode.h
103 + *
104 + *  Copyright (c) 2022 Whamcloud
105 + */
106 +
107 +#ifndef _CRITICAL_ENCODE_H
108 +#define _CRITICAL_ENCODE_H
109 +
110 +#include <linux/ctype.h>
111 +
112 +/* Encoding/decoding routines inspired from yEnc principles.
113 + * We just take care of a few critical characters:
114 + * NULL, LF, CR, /, DEL and =.
115 + * If such a char is found, it is replaced with '=' followed by
116 + * the char value + 64.
117 + * All other chars are left untouched.
118 + * Efficiency of this encoding depends on the occurences of the
119 + * critical chars, but statistically on binary data it can be much higher
120 + * than base64 for instance.
121 + */
122 +static inline int critical_encode(const u8 *src, int len, char *dst)
123 +{
124 +       u8 *p = (u8 *)src, *q = dst;
125 +
126 +       while (p - src < len) {
127 +               /* escape NULL, LF, CR, /, DEL and = */
128 +               if (unlikely(*p == 0x0 || *p == 0xA || *p == 0xD ||
129 +                            *p == '/' || *p == 0x7F || *p == '=')) {
130 +                       *(q++) = '=';
131 +                       *(q++) = *(p++) + 64;
132 +               } else {
133 +                       *(q++) = *(p++);
134 +               }
135 +       }
136 +
137 +       return (char *)q - dst;
138 +}
139 +
140 +/* returns the number of chars encoding would produce */
141 +static inline int critical_chars(const u8 *src, int len)
142 +{
143 +       u8 *p = (u8 *)src;
144 +       int newlen = len;
145 +
146 +       while (p - src < len) {
147 +               /* NULL, LF, CR, /, DEL and = cost an additional '=' */
148 +               if (unlikely(*p == 0x0 || *p == 0xA || *p == 0xD ||
149 +                            *p == '/' || *p == 0x7F || *p == '='))
150 +                       newlen++;
151 +               p++;
152 +       }
153 +
154 +       return newlen;
155 +}
156 +
157 +/* decoding routine - returns the number of chars in output */
158 +static inline int critical_decode(const u8 *src, int len, char *dst)
159 +{
160 +       u8 *p = (u8 *)src, *q = dst;
161 +
162 +       while (p - src < len) {
163 +               if (unlikely(*p == '=')) {
164 +                       *(q++) = *(++p) - 64;
165 +                       p++;
166 +               } else {
167 +                       *(q++) = *(p++);
168 +               }
169 +       }
170 +
171 +       return (char *)q - dst;
172 +}
173 +
174 +#endif /* _CRITICAL_ENCODE_H */
175 diff -wur a/fs/ext4/namei.c b/fs/ext4/namei.c
176 --- a/fs/ext4/namei.c
177 +++ b/fs/ext4/namei.c
178 @@ -39,6 +39,7 @@
179  
180  #include "xattr.h"
181  #include "acl.h"
182 +#include "critical_encode.h"
183  
184  #include <trace/events/ext4.h>
185  /*
186 @@ -1494,9 +1494,9 @@ static void dx_insert_block(struct dx_fr
187   * `de != NULL' is guaranteed by caller.
188   */
189  static inline int ext4_match (int len, const char * const name,
190 -                             struct ext4_dir_entry_2 * de)
191 +                             struct ext4_dir_entry_2 * de, int denamelen)
192  {
193 -       if (len != de->name_len)
194 +       if (len != denamelen)
195                 return 0;
196         if (!de->inode)
197                 return 0;
198 @@ -1516,18 +1516,30 @@ int search_dir(struct buffer_head *bh,
199  {
200         struct ext4_dir_entry_2 * de;
201         char * dlimit;
202 -       int de_len;
203 +       int de_len, denamelen;
204         const char *name = d_name->name;
205         int namelen = d_name->len;
206 +       bool probablytrunc;
207  
208         de = (struct ext4_dir_entry_2 *)search_buf;
209         dlimit = search_buf + buf_size;
210 +       /* fname is probably truncated if it is the decoded representation of
211 +        * an encrypted filename not aligned on a 32-byte boundary
212 +        */
213 +       probablytrunc = !IS_LUSTRE_MOUNT(dir->i_sb) &&
214 +               EXT4_I(dir)->i_flags & EXT4_ENCRYPT_FL && namelen & 31;
215         while ((char *) de < dlimit) {
216                 /* this code is executed quadratically often */
217                 /* do minimal checking `by hand' */
218 -
219 +               denamelen = de->name_len;
220 +               if (unlikely(probablytrunc) && de->name_len > namelen)
221 +                       /* Adjust name len to look for a partial match.
222 +                        * Since it is binary encrypted names, there
223 +                        * should not be any collision between names.
224 +                        */
225 +                       denamelen = namelen;
226                 if ((char *) de + namelen <= dlimit &&
227 -                   ext4_match (namelen, name, de)) {
228 +                   ext4_match(namelen, name, de, denamelen)) {
229                         /* found a match - just to be sure, do a full check */
230                         if (ext4_check_dir_entry(dir, NULL, de, bh, bh->b_data,
231                                                  bh->b_size, offset))
232 @@ -1588,8 +1589,10 @@ struct buffer_head *__ext4_find_entry
233                                    buffer */
234         int num = 0;
235         ext4_lblk_t  nblocks;
236 +       char *buf = NULL;
237         int i, err = 0;
238         int namelen;
239 +       struct qstr qstr;
240  
241         *res_dir = NULL;
242         sb = dir->i_sb;
243 @@ -1597,6 +1600,24 @@ struct buffer_head *__ext4_find_entry
244         if (namelen > EXT4_NAME_LEN)
245                 return NULL;
246  
247 +       if (unlikely(!IS_LUSTRE_MOUNT(dir->i_sb)) &&
248 +           EXT4_I(dir)->i_flags & EXT4_ENCRYPT_FL &&
249 +           strnchr(d_name->name, d_name->len, '=')) {
250 +               /* Only proceed to critical decode if
251 +                * iname contains escape char '='.
252 +                */
253 +               int len = d_name->len;
254 +
255 +               buf = kmalloc(len, GFP_NOFS);
256 +               if (!buf)
257 +                       return ERR_PTR(-ENOMEM);
258 +
259 +               len = critical_decode(d_name->name, len, buf);
260 +               qstr.name = buf;
261 +               qstr.len = len;
262 +               d_name = &qstr;
263 +       }
264 +
265         if (ext4_has_inline_data(dir)) {
266                 int has_inline_data = 1;
267                 ret = ext4_find_inline_entry(dir, d_name, res_dir,
268 @@ -1604,6 +1624,6 @@ struct buffer_head *__ext4_find_entry
269                 if (has_inline_data) {
270                         if (inlined)
271                                 *inlined = 1;
272 -                       return ret;
273 +                       goto out_free;
274                 }
275         }
276 @@ -1625,12 +1647,18 @@ struct buffer_head *__ext4_find_entry
277                  * return.  Otherwise, fall back to doing a search the
278                  * old fashioned way.
279                  */
280 -               if (err == -ENOENT)
281 -                       return NULL;
282 +               if (err == -ENOENT) {
283 +                       ret = NULL;
284 +                       goto out_free;
285 +               }
286 -               if (err && err != ERR_BAD_DX_DIR)
287 -                       return ERR_PTR(err);
288 +               if (err && err != ERR_BAD_DX_DIR) {
289 +                       ret = ERR_PTR(err);
290 +                       goto out_free;
291 +               }
292 -               if (bh)
293 -                       return bh;
294 +               if (bh) {
295 +                       ret = bh;
296 +                       goto out_free;
297 +               }
298                 dxtrace(printk(KERN_DEBUG "ext4_find_entry: dx failed, "
299                                "falling back\n"));
300                 ext4_htree_safe_relock(lck);
301 @@ -1667,8 +1698,10 @@ restart:
302                                 num++;
303                                 bh = ext4_getblk(NULL, dir, b++, 0, &err);
304                                 if (unlikely(err)) {
305 -                                       if (ra_max == 0)
306 -                                               return ERR_PTR(err);
307 +                                       if (ra_max == 0) {
308 +                                               ret = ERR_PTR(err);
309 +                                               goto out_free;
310 +                                       }
311                                         break;
312                                 }
313                                 bh_use[ra_max] = bh;
314 @@ -1729,6 +1763,9 @@ cleanup_and_exit:
315         /* Clean up the read-ahead blocks */
316         for (; ra_ptr < ra_max; ra_ptr++)
317                 brelse(bh_use[ra_ptr]);
318 +out_free:
319 +       if (buf)
320 +               kfree(buf);
321         return ret;
322  }
323  EXPORT_SYMBOL(__ext4_find_entry);
324 @@ -2121,7 +2128,7 @@ int ext4_find_dest_de(struct inode *d
325                 if (ext4_check_dir_entry(dir, NULL, de, bh,
326                                          buf, buf_size, offset))
327                         return -EIO;
328 -               if (ext4_match(namelen, name, de))
329 +               if (ext4_match(namelen, name, de, de->name_len))
330                         return -EEXIST;
331                 nlen = EXT4_DIR_REC_LEN(de);
332                 rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);