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
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:
12 { encoding: base64url, size: 3012, enc_ctx: YWJjZGVmZ2hpamtsbW
13 5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbg, enc_name: ZmlsZXdpdGh2ZX
14 J5bG9uZ25hbWVmaWxld2l0aHZlcnlsb25nbmFtZWZpbGV3aXRodmVyeWxvbmdu
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'.
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
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>
37 diff -wur /dev/null b/fs/ext4/encdata.h
39 +++ b/fs/ext4/encdata.h
44 + * Copyright (c) 2022 Whamcloud
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
65 +extern char encdata_xattr_fmt[NAME_MAX];
68 + * base64url encoding, lifted from fs/crypto/fname.c.
71 +static const char base64url_table[] =
72 + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
74 +#define BASE64URL_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3)
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.
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.
87 + * Return: the length of the resulting base64url-encoded string in bytes.
88 + * This will be equal to LLCRYPT_BASE64URL_CHARS(srclen).
90 +static inline int base64url_encode(const u8 *src, int srclen, char *dst)
97 + for (i = 0; i < srclen; i++) {
98 + ac = (ac << 8) | src[i];
102 + *cp++ = base64url_table[(ac >> bits) & 0x3f];
103 + } while (bits >= 6);
106 + *cp++ = base64url_table[(ac << (6 - bits)) & 0x3f];
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
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.
120 + * This implementation hasn't been optimized for performance.
122 + * Return: the length of the resulting decoded binary data in bytes,
123 + * or -1 if the string isn't a valid base64url string.
125 +static inline int base64url_decode(const char *src, int srclen, u8 *dst)
132 + for (i = 0; i < srclen; i++) {
133 + const char *p = strchr(base64url_table, src[i]);
135 + if (p == NULL || src[i] == 0)
137 + ac = (ac << 6) | (p - base64url_table);
141 + *bp++ = (u8)(ac >> bits);
144 + if (ac & ((1 << bits) - 1))
149 +/* This version of the code uses base64url encoding for binary data. */
150 +#define ENCDATA_ENCODING "base64url"
152 +/* Wrappers to support various encodings. Add new methods in there.
154 +static inline int encode(const u8 *src, int srclen, char *dst, char *encoding)
156 + if (!strcmp(encoding, "base64url"))
157 + return base64url_encode(src, srclen, dst);
161 +static inline int decode(const char *src, int srclen, u8 *dst, char *encoding)
163 + if (!strcmp(encoding, "base64url"))
164 + return base64url_decode(src, srclen, dst);
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
175 #include "truncate.h"
176 +#include "encdata.h"
178 #include <trace/events/ext4.h>
180 @@ -5069,6 +5070,11 @@ int ext4_getattr(struct vfsmount *mnt
182 inode = dentry->d_inode;
183 generic_fillattr(inode, stat);
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);
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
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"
202 extern ssize_t ext4_listxattr(struct dentry *, char *, size_t);
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
208 #include <linux/slab.h>
209 #include "ext4_jbd2.h"
211 +#include "critical_encode.h"
212 +#include "encdata.h"
216 @@ -27,12 +29,217 @@ ext4_xattr_security_list(struct dentr
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:
224 + * { encoding: base64url, size: 3012, enc_ctx: YWJjZGVmZ2hpamtsbW
225 + * 5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbg, enc_name: ZmlsZXdpdGh2ZX
226 + * J5bG9uZ25hbWVmaWxld2l0aHZlcnlsb25nbmFtZWZpbGV3aXRodmVyeWxvbmdu
227 + * YW1lZmlsZXdpdGg }
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'.
236 +static int ext4_build_xattr_encdata(struct dentry *dentry,
237 + struct inode *inode,
238 + void *buffer, size_t size)
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;
250 + if (!(EXT4_I(inode)->i_flags & EXT4_ENCRYPT_FL)) {
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)) {
264 + if (dentry && dentry->d_parent)
265 + parent = dentry->d_parent->d_inode;
267 + retval = ext4_setup_filename(parent, &dentry->d_name, 1, &qstr);
271 + /* base64url-encode raw name */
272 + encoded_name_len = encode(qstr.name, qstr.len, encoded_name,
274 + if (encoded_name_len == -EINVAL) {
278 + encoded_name[encoded_name_len] = '\0';
281 + /* Return exact xattr length we would return if called with
284 + retval = sizeof(ENCDATA_XATTR_FMT_COMP) - 1 +
285 + sizeof(ENCDATA_ENCODING) - 1 + strlen(size_str) +
286 + BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE) +
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));
299 + /* base64url-encode encryption context */
300 + encoded_enc_ctx_len = encode(enc_ctx, retval, encoded_enc_ctx,
302 + if (encoded_enc_ctx_len == -EINVAL) {
306 + encoded_enc_ctx[encoded_enc_ctx_len] = '\0';
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)
319 + if (qstr.name != dentry->d_name.name)
325 +static int ext4_process_xattr_encdata(struct inode *inode,
326 + const void *value, size_t size,
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;
340 + if (EXT4_I(inode)->i_flags & EXT4_ENCRYPT_FL ||
341 + !value || flags & XATTR_REPLACE) {
346 + buffer = kmalloc(size + 1, GFP_NOFS);
351 + memcpy(buffer, value, size);
352 + buffer[size] = '\0';
354 + retval = sscanf(buffer, encdata_xattr_fmt,
355 + encoding, &disk_size, encoded_enc_ctx, encoded_name);
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) {
369 + if (strlen(encoded_enc_ctx) >
370 + BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE)) {
375 + /* base64url-decode encryption context */
376 + retval = decode(encoded_enc_ctx, strlen(encoded_enc_ctx),
377 + enc_ctx, encoding);
382 + enc_ctx_len = retval;
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);
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);
400 + /* put raw encrypted name in EXT4_XATTR_NAME_RAWENCNAME xattr,
401 + * for later use, but base64url-decode first
403 + retval = decode(encoded_name, strlen(encoded_name), name, encoding);
410 + retval = ext4_xattr_set(inode, EXT4_XATTR_INDEX_LUSTRE,
411 + EXT4_XATTR_NAME_RAWENCNAME,
412 + name, name_len, XATTR_CREATE);
421 ext4_xattr_security_get(struct dentry *dentry, const char *name,
422 void *buffer, size_t size, int type)
424 if (strcmp(name, "") == 0)
427 + if (!strncmp(name, EXT4_XATTR_NAME_ENCDATA, strlen(name)))
428 + return ext4_build_xattr_encdata(dentry, dentry->d_inode,
431 return ext4_xattr_get(dentry->d_inode, EXT4_XATTR_INDEX_SECURITY,
434 @@ -43,6 +235,11 @@ ext4_xattr_security_set(struct dentry
436 if (strcmp(name, "") == 0)
439 + if (!strncmp(name, EXT4_XATTR_NAME_ENCDATA, strlen(name)))
440 + return ext4_process_xattr_encdata(dentry->d_inode, value,
443 return ext4_xattr_set(dentry->d_inode, EXT4_XATTR_INDEX_SECURITY,
444 name, value, size, flags);
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
453 +#include "encdata.h"
455 #define CREATE_TRACE_POINTS
456 #include <trace/events/ext4.h>
457 @@ -6133,7 +6134,8 @@ MODULE_ALIAS_FS("ext4");
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];
464 static int __init ext4_init_fs(void)
466 @@ -6178,6 +6180,12 @@ static int __init ext4_init_fs(void)
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));
478 unregister_as_ext2();