Whamcloud - gitweb
New tag 2.15.63
[fs/lustre-release.git] / ldiskfs / kernel_patches / patches / rhel7.9 / ext4-encdata.patch
1 commit d0a722cb8fb886380e24e8261e8efca09a3262d6
2 Author:     Sebastien Buisson <sbuisson@ddn.com>
3 AuthorDate: Tue Dec 20 15:40:52 2022 +0100
4 Commit:     Oleg Drokin <green@whamcloud.com>
5 CommitDate: Thu Aug 31 06:28:45 2023 +0000
6 LU-16374 ldiskfs: implement security.encdata xattr
7
8 security.encdata is a virtual xattr containing information related
9 to encrypted files. It is expressed as ASCII text with a "key: value"
10 format, and space as field separator. For instance:
11
12    { encoding: base64url, size: 3012, enc_ctx: YWJjZGVmZ2hpamtsbW
13    5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbg, enc_name: ZmlsZXdpdGh2ZX
14    J5bG9uZ25hbWVmaWxld2l0aHZlcnlsb25nbmFtZWZpbGV3aXRodmVyeWxvbmdu
15    YW1lZmlsZXdpdGg }
16
17 'encoding' is the encoding method used for binary data, assume name
18 can be up to 255 chars.
19 'size' is the clear text file data length in bytes.
20 'enc_ctx' is encoded encryption context, 40 bytes for v2.
21 'enc_name' is encoded encrypted name, 256 bytes max.
22 So on overall, this xattr is at most 727 chars plus terminating '0'.
23
24 On get, the value of the security.encdata xattr is computed from
25 encrypted file's information.
26 On set, encrypted file's information is restored from xattr value.
27 The encrypted name is stored temporarily in a dedicated xattr
28 LDISKFS_XATTR_NAME_RAWENCNAME, that will be used to set correct name
29 at linkat.
30
31 Signed-off-by: Sebastien Buisson <sbuisson@ddn.com>
32 Change-Id: Ia318c39d403b1c448e71bcd5b29862d022d05d0a
33 Reviewed-on: https://review.whamcloud.com/49456
34 Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
35 Reviewed-by: Li Dongyang <dongyangli@ddn.com>
36
37 diff -wur /dev/null b/fs/ext4/encdata.h
38 --- /dev/null
39 +++ b/fs/ext4/encdata.h
40 @@ -0,0 +1,128 @@
41 +/*
42 + *  encdata.h
43 + *
44 + *  Copyright (c) 2022 Whamcloud
45 + */
46 +
47 +#ifndef _ENCDATA_H
48 +#define _ENCDATA_H
49 +
50 +/* Define a fixed 4096-byte encryption unit size */
51 +/* Must be identical to LUSTRE_ENCRYPTION_UNIT_SIZE */
52 +#define EXT4_ENCRYPTION_BLOCKBITS 12
53 +#define EXT4_ENCRYPTION_UNIT_SIZE ((size_t)1 << EXT4_ENCRYPTION_BLOCKBITS)
54 +#define EXT4_ENCRYPTION_MASK      (~(EXT4_ENCRYPTION_UNIT_SIZE - 1))
55 +#define LLCRYPT_SET_CONTEXT_MAX_SIZE   40
56 +#define ENCDATA_XATTR_FMT_1 "{ encoding: "
57 +#define ENCDATA_XATTR_FMT_2 ", size: "
58 +#define ENCDATA_XATTR_FMT_3 ", enc_ctx: "
59 +#define ENCDATA_XATTR_FMT_4 ", enc_name: "
60 +#define ENCDATA_XATTR_FMT_END " }"
61 +#define ENCDATA_XATTR_FMT_COMP  ENCDATA_XATTR_FMT_1 ENCDATA_XATTR_FMT_2 \
62 +                               ENCDATA_XATTR_FMT_3 ENCDATA_XATTR_FMT_4 \
63 +                               ENCDATA_XATTR_FMT_END
64 +
65 +extern char encdata_xattr_fmt[NAME_MAX];
66 +
67 +/*
68 + * base64url encoding, lifted from fs/crypto/fname.c.
69 + */
70 +
71 +static const char base64url_table[] =
72 +       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
73 +
74 +#define BASE64URL_CHARS(nbytes)        DIV_ROUND_UP((nbytes) * 4, 3)
75 +
76 +/**
77 + * base64url_encode() - base64url-encode some binary data
78 + * @src: the binary data to encode
79 + * @srclen: the length of @src in bytes
80 + * @dst: (output) the base64url-encoded string.  Not NUL-terminated.
81 + *
82 + * Encodes data using base64url encoding, i.e. the "Base 64 Encoding with URL
83 + * and Filename Safe Alphabet" specified by RFC 4648.  '='-padding isn't used,
84 + * as it's unneeded and not required by the RFC.  base64url is used instead of
85 + * base64 to avoid the '/' character, which isn't allowed in filenames.
86 + *
87 + * Return: the length of the resulting base64url-encoded string in bytes.
88 + *        This will be equal to LLCRYPT_BASE64URL_CHARS(srclen).
89 + */
90 +static inline int base64url_encode(const u8 *src, int srclen, char *dst)
91 +{
92 +       u32 ac = 0;
93 +       int bits = 0;
94 +       int i;
95 +       char *cp = dst;
96 +
97 +       for (i = 0; i < srclen; i++) {
98 +               ac = (ac << 8) | src[i];
99 +               bits += 8;
100 +               do {
101 +                       bits -= 6;
102 +                       *cp++ = base64url_table[(ac >> bits) & 0x3f];
103 +               } while (bits >= 6);
104 +       }
105 +       if (bits)
106 +               *cp++ = base64url_table[(ac << (6 - bits)) & 0x3f];
107 +       return cp - dst;
108 +}
109 +
110 +/**
111 + * base64url_decode() - base64url-decode a string
112 + * @src: the string to decode.  Doesn't need to be NUL-terminated.
113 + * @srclen: the length of @src in bytes
114 + * @dst: (output) the decoded binary data
115 + *
116 + * Decodes a string using base64url encoding, i.e. the "Base 64 Encoding with
117 + * URL and Filename Safe Alphabet" specified by RFC 4648.  '='-padding isn't
118 + * accepted, nor are non-encoding characters such as whitespace.
119 + *
120 + * This implementation hasn't been optimized for performance.
121 + *
122 + * Return: the length of the resulting decoded binary data in bytes,
123 + *        or -1 if the string isn't a valid base64url string.
124 + */
125 +static inline int base64url_decode(const char *src, int srclen, u8 *dst)
126 +{
127 +       u32 ac = 0;
128 +       int bits = 0;
129 +       int i;
130 +       u8 *bp = dst;
131 +
132 +       for (i = 0; i < srclen; i++) {
133 +               const char *p = strchr(base64url_table, src[i]);
134 +
135 +               if (p == NULL || src[i] == 0)
136 +                       return -1;
137 +               ac = (ac << 6) | (p - base64url_table);
138 +               bits += 6;
139 +               if (bits >= 8) {
140 +                       bits -= 8;
141 +                       *bp++ = (u8)(ac >> bits);
142 +               }
143 +       }
144 +       if (ac & ((1 << bits) - 1))
145 +               return -1;
146 +       return bp - dst;
147 +}
148 +
149 +/* This version of the code uses base64url encoding for binary data. */
150 +#define ENCDATA_ENCODING       "base64url"
151 +
152 +/* Wrappers to support various encodings. Add new methods in there.
153 + */
154 +static inline int encode(const u8 *src, int srclen, char *dst, char *encoding)
155 +{
156 +       if (!strcmp(encoding, "base64url"))
157 +               return base64url_encode(src, srclen, dst);
158 +       return -EINVAL;
159 +}
160 +
161 +static inline int decode(const char *src, int srclen, u8 *dst, char *encoding)
162 +{
163 +       if (!strcmp(encoding, "base64url"))
164 +               return base64url_decode(src, srclen, dst);
165 +       return -EINVAL;
166 +}
167 +
168 +#endif /* _ENCDATA_H */
169 diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c
170 --- a/fs/ext4/inode.c
171 +++ b/fs/ext4/inode.c
172 @@ -45,6 +45,7 @@
173  #include "xattr.h"
174  #include "acl.h"
175  #include "truncate.h"
176 +#include "encdata.h"
177  
178  #include <trace/events/ext4.h>
179  
180 @@ -5069,6 +5070,11 @@ int ext4_getattr(struct vfsmount *mnt
181  
182         inode = dentry->d_inode;
183         generic_fillattr(inode, stat);
184 +
185 +       if (EXT4_I(inode)->i_flags & EXT4_ENCRYPT_FL &&
186 +           unlikely(!IS_LUSTRE_MOUNT(inode->i_sb)))
187 +               stat->size = round_up(stat->size,
188 +                                     EXT4_ENCRYPTION_UNIT_SIZE);
189
190         /*
191          * If there is inline data in the inode, the inode will normally not
192 diff -wur a/fs/ext4/xattr.h b/fs/ext4/xattr.h
193 --- a/fs/ext4/xattr.h
194 +++ b/fs/ext4/xattr.h
195 @@ -123,6 +123,8 @@ extern const struct xattr_handler ext4_x
196  
197  #define EXT4_XATTR_INDEX_ENCRYPTION            9
198  #define EXT4_XATTR_NAME_ENCRYPTION_CONTEXT "c"
199 +#define EXT4_XATTR_NAME_ENCDATA              "encdata"
200 +#define EXT4_XATTR_NAME_RAWENCNAME           "rawencname"
201  
202  extern ssize_t ext4_listxattr(struct dentry *, char *, size_t);
203  
204 diff -wur a/fs/ext4/xattr_security.c b/fs/ext4/xattr_security.c
205 --- a/fs/ext4/xattr_security.c
206 +++ b/fs/ext4/xattr_security.c
207 @@ -9,6 +9,8 @@
208  #include <linux/slab.h>
209  #include "ext4_jbd2.h"
210  #include "ext4.h"
211 +#include "critical_encode.h"
212 +#include "encdata.h"
213  #include "xattr.h"
214  
215  static size_t
216 @@ -27,12 +29,217 @@ ext4_xattr_security_list(struct dentr
217         return total_len;
218  }
219  
220 +/* security.encdata is a virtual xattr containing information related
221 + * to encrypted files. It is expressed as ASCII text with a "key: value"
222 + * format, and space as field separator. For instance:
223 + *
224 + *    { encoding: base64url, size: 3012, enc_ctx: YWJjZGVmZ2hpamtsbW
225 + *    5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbg, enc_name: ZmlsZXdpdGh2ZX
226 + *    J5bG9uZ25hbWVmaWxld2l0aHZlcnlsb25nbmFtZWZpbGV3aXRodmVyeWxvbmdu
227 + *    YW1lZmlsZXdpdGg }
228 + *
229 + * 'encoding' is the encoding method used for binary data, assume name
230 + * can be up to 255 chars.
231 + * 'size' is the clear text file data length in bytes.
232 + * 'enc_ctx' is encoded encryption context, 40 bytes for v2.
233 + * 'enc_name' is encoded encrypted name, 256 bytes max.
234 + * So on overall, this xattr is at most 727 chars plus terminating '\0'.
235 + */
236 +static int ext4_build_xattr_encdata(struct dentry *dentry,
237 +                                    struct inode *inode,
238 +                                    void *buffer, size_t size)
239 +{
240 +       char encoded_enc_ctx[BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE) + 1];
241 +       unsigned char enc_ctx[LLCRYPT_SET_CONTEXT_MAX_SIZE];
242 +       char encoded_name[BASE64URL_CHARS(NAME_MAX) + 1];
243 +       struct qstr qstr = QSTR_INIT(NULL, 0);
244 +       struct inode *parent = NULL;
245 +       int encoded_enc_ctx_len = 0;
246 +       int encoded_name_len = 0;
247 +       char size_str[32];
248 +       int retval;
249 +
250 +       if (!(EXT4_I(inode)->i_flags & EXT4_ENCRYPT_FL)) {
251 +               retval = -ENODATA;
252 +               goto out;
253 +       }
254 +
255 +       /* get size */
256 +       retval = snprintf(size_str, sizeof(size_str), "%llu",
257 +                         S_ISDIR(inode->i_mode) ? 0 : inode->i_size);
258 +       if (retval >= sizeof(size_str)) {
259 +               retval = -ERANGE;
260 +               goto out;
261 +       }
262 +
263 +       /* get raw name */
264 +       if (dentry && dentry->d_parent)
265 +               parent = dentry->d_parent->d_inode;
266 +
267 +       retval = ext4_setup_filename(parent, &dentry->d_name, 1, &qstr);
268 +       if (retval)
269 +               goto out;
270 +
271 +       /* base64url-encode raw name */
272 +       encoded_name_len = encode(qstr.name, qstr.len, encoded_name,
273 +                                 ENCDATA_ENCODING);
274 +       if (encoded_name_len == -EINVAL) {
275 +               retval = -EINVAL;
276 +               goto out;
277 +       }
278 +       encoded_name[encoded_name_len] = '\0';
279 +
280 +       if (!buffer) {
281 +               /* Return exact xattr length we would return if called with
282 +                * non-NULL buffer.
283 +                */
284 +               retval = sizeof(ENCDATA_XATTR_FMT_COMP) - 1 +
285 +                       sizeof(ENCDATA_ENCODING) - 1 + strlen(size_str) +
286 +                       BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE) +
287 +                       encoded_name_len;
288 +               goto out;
289 +       }
290 +
291 +       /* get encryption context */
292 +       retval = ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION,
293 +                               EXT4_XATTR_NAME_ENCRYPTION_CONTEXT,
294 +                               enc_ctx, sizeof(enc_ctx));
295 +
296 +       if (retval < 0)
297 +               goto out;
298 +
299 +       /* base64url-encode encryption context */
300 +       encoded_enc_ctx_len = encode(enc_ctx, retval, encoded_enc_ctx,
301 +                                    ENCDATA_ENCODING);
302 +       if (encoded_enc_ctx_len == -EINVAL) {
303 +               retval = -EINVAL;
304 +               goto out;
305 +       }
306 +       encoded_enc_ctx[encoded_enc_ctx_len] = '\0';
307 +
308 +       /* write EXT4_XATTR_ENCDATA info into buffer */
309 +       retval = snprintf(buffer, size,
310 +                         ENCDATA_XATTR_FMT_1 ENCDATA_ENCODING
311 +                         ENCDATA_XATTR_FMT_2"%s"ENCDATA_XATTR_FMT_3"%s"
312 +                         ENCDATA_XATTR_FMT_4"%s"ENCDATA_XATTR_FMT_END,
313 +                         size_str, encoded_enc_ctx,
314 +                         encoded_name_len ? encoded_name : "");
315 +       if (retval >= size)
316 +               retval = -ERANGE;
317 +
318 +out:
319 +       if (qstr.name != dentry->d_name.name)
320 +               kfree(qstr.name);
321 +
322 +       return retval;
323 +}
324 +
325 +static int ext4_process_xattr_encdata(struct inode *inode,
326 +                                      const void *value, size_t size,
327 +                                      int flags)
328 +{
329 +       char encoded_enc_ctx[BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE) + 1];
330 +       unsigned char enc_ctx[LLCRYPT_SET_CONTEXT_MAX_SIZE];
331 +       char encoded_name[BASE64URL_CHARS(NAME_MAX) + 1];
332 +       char encoding[NAME_MAX + 1];
333 +       char name[NAME_MAX + 1];
334 +       loff_t disk_size = 0;
335 +       char *buffer = NULL;
336 +       int enc_ctx_len = 0;
337 +       int name_len = 0;
338 +       int retval = 0;
339 +
340 +       if (EXT4_I(inode)->i_flags & EXT4_ENCRYPT_FL ||
341 +           !value || flags & XATTR_REPLACE) {
342 +               retval = -EINVAL;
343 +               goto out;
344 +       }
345 +
346 +       buffer = kmalloc(size + 1, GFP_NOFS);
347 +       if (!buffer) {
348 +               retval = -ENOMEM;
349 +               goto out;
350 +       }
351 +       memcpy(buffer, value, size);
352 +       buffer[size] = '\0';
353 +
354 +       retval = sscanf(buffer, encdata_xattr_fmt,
355 +                       encoding, &disk_size, encoded_enc_ctx, encoded_name);
356 +       if (retval < 4) {
357 +               retval = -EINVAL;
358 +               goto out;
359 +       }
360 +
361 +       /* get former encryption context: should not exist */
362 +       retval = ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION,
363 +                               EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, NULL, 0);
364 +       if (retval != -ENODATA) {
365 +               retval = -EINVAL;
366 +               goto out;
367 +       }
368 +
369 +       if (strlen(encoded_enc_ctx) >
370 +           BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE)) {
371 +               retval = -EINVAL;
372 +               goto out;
373 +       }
374 +
375 +       /* base64url-decode encryption context */
376 +       retval = decode(encoded_enc_ctx, strlen(encoded_enc_ctx),
377 +                       enc_ctx, encoding);
378 +       if (retval < 0) {
379 +               retval = -EINVAL;
380 +               goto out;
381 +       }
382 +       enc_ctx_len = retval;
383 +
384 +       /* set encryption context, this will set encryption flag */
385 +       retval = ext4_xattr_set(inode, EXT4_XATTR_INDEX_ENCRYPTION,
386 +                               EXT4_XATTR_NAME_ENCRYPTION_CONTEXT,
387 +                               enc_ctx, enc_ctx_len, XATTR_CREATE);
388 +       if (retval < 0)
389 +               goto out;
390 +
391 +       if (disk_size) {
392 +               /* set size on inode */
393 +               spin_lock(&inode->i_lock);
394 +               i_size_write(inode, disk_size);
395 +               EXT4_I(inode)->i_disksize = disk_size;
396 +               spin_unlock(&inode->i_lock);
397 +               mark_inode_dirty(inode);
398 +       }
399 +
400 +       /* put raw encrypted name in EXT4_XATTR_NAME_RAWENCNAME xattr,
401 +        * for later use, but base64url-decode first
402 +        */
403 +       retval = decode(encoded_name, strlen(encoded_name), name, encoding);
404 +       if (retval < 0) {
405 +               retval = -EINVAL;
406 +               goto out;
407 +       }
408 +       name_len = retval;
409 +
410 +       retval = ext4_xattr_set(inode, EXT4_XATTR_INDEX_LUSTRE,
411 +                               EXT4_XATTR_NAME_RAWENCNAME,
412 +                               name, name_len, XATTR_CREATE);
413 +
414 +out:
415 +       kfree(buffer);
416 +
417 +       return retval;
418 +}
419 +
420  static int
421  ext4_xattr_security_get(struct dentry *dentry, const char *name,
422                        void *buffer, size_t size, int type)
423  {
424         if (strcmp(name, "") == 0)
425                 return -EINVAL;
426 +
427 +       if (!strncmp(name, EXT4_XATTR_NAME_ENCDATA, strlen(name)))
428 +               return ext4_build_xattr_encdata(dentry, dentry->d_inode,
429 +                                                  buffer, size);
430 +
431         return ext4_xattr_get(dentry->d_inode, EXT4_XATTR_INDEX_SECURITY,
432                               name, buffer, size);
433  }
434 @@ -43,6 +235,11 @@ ext4_xattr_security_set(struct dentry
435  {
436         if (strcmp(name, "") == 0)
437                 return -EINVAL;
438 +
439 +       if (!strncmp(name, EXT4_XATTR_NAME_ENCDATA, strlen(name)))
440 +               return ext4_process_xattr_encdata(dentry->d_inode, value,
441 +                                                    size, flags);
442 +
443         return ext4_xattr_set(dentry->d_inode, EXT4_XATTR_INDEX_SECURITY,
444                               name, value, size, flags);
445  }
446 diff -wur a/fs/ext4/super.c b/fs/ext4/super.c
447 --- a/fs/ext4/super.c
448 +++ b/fs/ext4/super.c
449 @@ -53,6 +53,7 @@
450  #include "xattr.h"
451  #include "acl.h"
452  #include "mballoc.h"
453 +#include "encdata.h"
454  
455  #define CREATE_TRACE_POINTS
456  #include <trace/events/ext4.h>
457 @@ -6133,7 +6134,8 @@ MODULE_ALIAS_FS("ext4");
458  
459  /* Shared across all ext4 file systems */
460  wait_queue_head_t ext4__ioend_wq[EXT4_WQ_HASH_SZ];
461  struct mutex ext4__aio_mutex[EXT4_WQ_HASH_SZ];
462 +char encdata_xattr_fmt[NAME_MAX];
463  
464  static int __init ext4_init_fs(void)
465  {
466 @@ -6178,6 +6180,12 @@ static int __init ext4_init_fs(void)
467         if (err)
468                 goto out;
469  
470 +       snprintf(encdata_xattr_fmt, sizeof(encdata_xattr_fmt),
471 +                ENCDATA_XATTR_FMT_1"%%%u[^,]"ENCDATA_XATTR_FMT_2"%%llu"
472 +                ENCDATA_XATTR_FMT_3"%%%us"ENCDATA_XATTR_FMT_4"%%%us",
473 +                NAME_MAX, BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE),
474 +                BASE64URL_CHARS(NAME_MAX));
475 +
476         return 0;
477  out:
478         unregister_as_ext2();