1 diff -wur a/fs/ext4/dir.c b/fs/ext4/dir.c
5 #include <linux/rbtree.h>
8 +#include "critical_encode.h"
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.
15 u64 version = filp->f_version;
16 + int presented_len = de->name_len;
17 + char *buf = de->name;
19 - error = filldir(dirent, de->name,
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,
25 + if (unlikely(de->name_len != presented_len)) {
26 + buf = kmalloc(presented_len + 1,
32 + critical_encode(de->name,
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
39 + presented_len = NAME_MAX;
40 + if (buf[NAME_MAX - 1] == '=')
43 + buf[presented_len] = '\0';
46 + error = filldir(dirent, buf,
49 le32_to_cpu(de->inode),
50 get_dtype(sb, de->file_type));
51 + if (unlikely(de->name_len != presented_len))
55 if (version != filp->f_version)
56 @@ -516,10 +542,38 @@ static int call_filldir(struct file *fil
58 curr_pos = hash2pos(filp, fname->hash, fname->minor_hash);
60 - error = filldir(dirent, fname->name,
61 - fname->name_len, curr_pos,
62 + int presented_len = fname->name_len;
63 + char *buf = fname->name;
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,
69 + if (unlikely(fname->name_len != presented_len)) {
70 + buf = kmalloc(presented_len + 1, GFP_NOFS);
72 + filp->f_pos = curr_pos;
73 + info->extra_fname = fname;
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
82 + presented_len = NAME_MAX;
83 + if (buf[presented_len - 1] == '=')
86 + buf[presented_len] = '\0';
88 + error = filldir(dirent, buf,
89 + presented_len, curr_pos,
91 get_dtype(sb, fname->file_type));
92 + if (unlikely(fname->name_len != presented_len))
95 filp->f_pos = curr_pos;
96 info->extra_fname = fname;
97 diff -wur /dev/null b/fs/ext4/critical_encode.h
99 +++ b/fs/ext4/critical_encode.h
102 + * critical_encode.h
104 + * Copyright (c) 2022 Whamcloud
107 +#ifndef _CRITICAL_ENCODE_H
108 +#define _CRITICAL_ENCODE_H
110 +#include <linux/ctype.h>
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.
122 +static inline int critical_encode(const u8 *src, int len, char *dst)
124 + u8 *p = (u8 *)src, *q = dst;
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 == '=')) {
131 + *(q++) = *(p++) + 64;
137 + return (char *)q - dst;
140 +/* returns the number of chars encoding would produce */
141 +static inline int critical_chars(const u8 *src, int len)
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 == '='))
157 +/* decoding routine - returns the number of chars in output */
158 +static inline int critical_decode(const u8 *src, int len, char *dst)
160 + u8 *p = (u8 *)src, *q = dst;
162 + while (p - src < len) {
163 + if (unlikely(*p == '=')) {
164 + *(q++) = *(++p) - 64;
171 + return (char *)q - dst;
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
182 +#include "critical_encode.h"
184 #include <trace/events/ext4.h>
186 @@ -1494,9 +1494,9 @@ static void dx_insert_block(struct dx_fr
187 * `de != NULL' is guaranteed by caller.
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)
193 - if (len != de->name_len)
194 + if (len != denamelen)
198 @@ -1516,18 +1516,30 @@ int search_dir(struct buffer_head *bh,
200 struct ext4_dir_entry_2 * de;
203 + int de_len, denamelen;
204 const char *name = d_name->name;
205 int namelen = d_name->len;
206 + bool probablytrunc;
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
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' */
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.
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,
232 @@ -1588,8 +1589,10 @@ struct buffer_head *__ext4_find_entry
243 @@ -1597,6 +1600,24 @@ struct buffer_head *__ext4_find_entry
244 if (namelen > EXT4_NAME_LEN)
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 '='.
253 + int len = d_name->len;
255 + buf = kmalloc(len, GFP_NOFS);
257 + return ERR_PTR(-ENOMEM);
259 + len = critical_decode(d_name->name, len, buf);
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) {
276 @@ -1625,12 +1647,18 @@ struct buffer_head *__ext4_find_entry
277 * return. Otherwise, fall back to doing a search the
280 - if (err == -ENOENT)
282 + if (err == -ENOENT) {
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);
298 dxtrace(printk(KERN_DEBUG "ext4_find_entry: dx failed, "
300 ext4_htree_safe_relock(lck);
301 @@ -1667,8 +1698,10 @@ restart:
303 bh = ext4_getblk(NULL, dir, b++, 0, &err);
306 - return ERR_PTR(err);
308 + ret = ERR_PTR(err);
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]);
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))
328 - if (ext4_match(namelen, name, de))
329 + if (ext4_match(namelen, name, de, de->name_len))
331 nlen = EXT4_DIR_REC_LEN(de);
332 rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);