Whamcloud - gitweb
LU-16374 ldiskfs: implement security.encdata xattr 56/49456/13
authorSebastien Buisson <sbuisson@ddn.com>
Tue, 20 Dec 2022 14:40:52 +0000 (15:40 +0100)
committerOleg Drokin <green@whamcloud.com>
Thu, 31 Aug 2023 06:28:45 +0000 (06:28 +0000)
security.encdata is a virtual xattr containing information related
to encrypted files. It is expressed as ASCII text with a "key: value"
format, and space as field separator. For instance:

   { encoding: base64url, size: 3012, enc_ctx: YWJjZGVmZ2hpamtsbW
   5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbg, enc_name: ZmlsZXdpdGh2ZX
   J5bG9uZ25hbWVmaWxld2l0aHZlcnlsb25nbmFtZWZpbGV3aXRodmVyeWxvbmdu
   YW1lZmlsZXdpdGg }

'encoding' is the encoding method used for binary data, assume name
can be up to 255 chars.
'size' is the clear text file data length in bytes.
'enc_ctx' is encoded encryption context, 40 bytes for v2.
'enc_name' is encoded encrypted name, 256 bytes max.
So on overall, this xattr is at most 727 chars plus terminating '0'.

On get, the value of the security.encdata xattr is computed from
encrypted file's information.
On set, encrypted file's information is restored from xattr value.
The encrypted name is stored temporarily in a dedicated xattr
LDISKFS_XATTR_NAME_RAWENCNAME, that will be used to set correct name
at linkat.

Signed-off-by: Sebastien Buisson <sbuisson@ddn.com>
Change-Id: Ia318c39d403b1c448e71bcd5b29862d022d05d0a
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/49456
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Li Dongyang <dongyangli@ddn.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
ldiskfs/kernel_patches/patches/rhel7.9/ext4-encdata.patch
ldiskfs/kernel_patches/patches/rhel7.9/ext4-filename-encode.patch
ldiskfs/kernel_patches/patches/rhel8/ext4-encdata.patch

index c2a1c68..f0c8645 100644 (file)
@@ -1,7 +1,7 @@
 diff -wur /dev/null b/fs/ext4/encdata.h
 --- /dev/null
 +++ b/fs/ext4/encdata.h
-@@ -0,0 +1,16 @@
+@@ -0,0 +1,128 @@
 +/*
 + *  encdata.h
 + *
@@ -16,6 +16,118 @@ diff -wur /dev/null b/fs/ext4/encdata.h
 +#define EXT4_ENCRYPTION_BLOCKBITS 12
 +#define EXT4_ENCRYPTION_UNIT_SIZE ((size_t)1 << EXT4_ENCRYPTION_BLOCKBITS)
 +#define EXT4_ENCRYPTION_MASK      (~(EXT4_ENCRYPTION_UNIT_SIZE - 1))
++#define LLCRYPT_SET_CONTEXT_MAX_SIZE  40
++#define ENCDATA_XATTR_FMT_1 "{ encoding: "
++#define ENCDATA_XATTR_FMT_2 ", size: "
++#define ENCDATA_XATTR_FMT_3 ", enc_ctx: "
++#define ENCDATA_XATTR_FMT_4 ", enc_name: "
++#define ENCDATA_XATTR_FMT_END " }"
++#define ENCDATA_XATTR_FMT_COMP  ENCDATA_XATTR_FMT_1 ENCDATA_XATTR_FMT_2 \
++                              ENCDATA_XATTR_FMT_3 ENCDATA_XATTR_FMT_4 \
++                              ENCDATA_XATTR_FMT_END
++
++extern char encdata_xattr_fmt[NAME_MAX];
++
++/*
++ * base64url encoding, lifted from fs/crypto/fname.c.
++ */
++
++static const char base64url_table[] =
++      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
++
++#define BASE64URL_CHARS(nbytes)       DIV_ROUND_UP((nbytes) * 4, 3)
++
++/**
++ * base64url_encode() - base64url-encode some binary data
++ * @src: the binary data to encode
++ * @srclen: the length of @src in bytes
++ * @dst: (output) the base64url-encoded string.  Not NUL-terminated.
++ *
++ * Encodes data using base64url encoding, i.e. the "Base 64 Encoding with URL
++ * and Filename Safe Alphabet" specified by RFC 4648.  '='-padding isn't used,
++ * as it's unneeded and not required by the RFC.  base64url is used instead of
++ * base64 to avoid the '/' character, which isn't allowed in filenames.
++ *
++ * Return: the length of the resulting base64url-encoded string in bytes.
++ *       This will be equal to LLCRYPT_BASE64URL_CHARS(srclen).
++ */
++static inline int base64url_encode(const u8 *src, int srclen, char *dst)
++{
++      u32 ac = 0;
++      int bits = 0;
++      int i;
++      char *cp = dst;
++
++      for (i = 0; i < srclen; i++) {
++              ac = (ac << 8) | src[i];
++              bits += 8;
++              do {
++                      bits -= 6;
++                      *cp++ = base64url_table[(ac >> bits) & 0x3f];
++              } while (bits >= 6);
++      }
++      if (bits)
++              *cp++ = base64url_table[(ac << (6 - bits)) & 0x3f];
++      return cp - dst;
++}
++
++/**
++ * base64url_decode() - base64url-decode a string
++ * @src: the string to decode.  Doesn't need to be NUL-terminated.
++ * @srclen: the length of @src in bytes
++ * @dst: (output) the decoded binary data
++ *
++ * Decodes a string using base64url encoding, i.e. the "Base 64 Encoding with
++ * URL and Filename Safe Alphabet" specified by RFC 4648.  '='-padding isn't
++ * accepted, nor are non-encoding characters such as whitespace.
++ *
++ * This implementation hasn't been optimized for performance.
++ *
++ * Return: the length of the resulting decoded binary data in bytes,
++ *       or -1 if the string isn't a valid base64url string.
++ */
++static inline int base64url_decode(const char *src, int srclen, u8 *dst)
++{
++      u32 ac = 0;
++      int bits = 0;
++      int i;
++      u8 *bp = dst;
++
++      for (i = 0; i < srclen; i++) {
++              const char *p = strchr(base64url_table, src[i]);
++
++              if (p == NULL || src[i] == 0)
++                      return -1;
++              ac = (ac << 6) | (p - base64url_table);
++              bits += 6;
++              if (bits >= 8) {
++                      bits -= 8;
++                      *bp++ = (u8)(ac >> bits);
++              }
++      }
++      if (ac & ((1 << bits) - 1))
++              return -1;
++      return bp - dst;
++}
++
++/* This version of the code uses base64url encoding for binary data. */
++#define ENCDATA_ENCODING      "base64url"
++
++/* Wrappers to support various encodings. Add new methods in there.
++ */
++static inline int encode(const u8 *src, int srclen, char *dst, char *encoding)
++{
++      if (!strcmp(encoding, "base64url"))
++              return base64url_encode(src, srclen, dst);
++      return -EINVAL;
++}
++
++static inline int decode(const char *src, int srclen, u8 *dst, char *encoding)
++{
++      if (!strcmp(encoding, "base64url"))
++              return base64url_decode(src, srclen, dst);
++      return -EINVAL;
++}
 +
 +#endif /* _ENCDATA_H */
 diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c
@@ -41,3 +153,290 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c
 
        /*
         * If there is inline data in the inode, the inode will normally not
+diff -wur a/fs/ext4/xattr.h b/fs/ext4/xattr.h
+--- a/fs/ext4/xattr.h
++++ b/fs/ext4/xattr.h
+@@ -123,6 +123,8 @@ extern const struct xattr_handler ext4_x
+ #define EXT4_XATTR_INDEX_ENCRYPTION           9
+ #define EXT4_XATTR_NAME_ENCRYPTION_CONTEXT "c"
++#define EXT4_XATTR_NAME_ENCDATA             "encdata"
++#define EXT4_XATTR_NAME_RAWENCNAME          "rawencname"
+ extern ssize_t ext4_listxattr(struct dentry *, char *, size_t);
+diff -wur a/fs/ext4/xattr_security.c b/fs/ext4/xattr_security.c
+--- a/fs/ext4/xattr_security.c
++++ b/fs/ext4/xattr_security.c
+@@ -9,6 +9,8 @@
+ #include <linux/slab.h>
+ #include "ext4_jbd2.h"
+ #include "ext4.h"
++#include "critical_encode.h"
++#include "encdata.h"
+ #include "xattr.h"
+ static size_t
+@@ -27,12 +29,217 @@ ext4_xattr_security_list(struct dentr
+       return total_len;
+ }
++/* security.encdata is a virtual xattr containing information related
++ * to encrypted files. It is expressed as ASCII text with a "key: value"
++ * format, and space as field separator. For instance:
++ *
++ *    { encoding: base64url, size: 3012, enc_ctx: YWJjZGVmZ2hpamtsbW
++ *    5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbg, enc_name: ZmlsZXdpdGh2ZX
++ *    J5bG9uZ25hbWVmaWxld2l0aHZlcnlsb25nbmFtZWZpbGV3aXRodmVyeWxvbmdu
++ *    YW1lZmlsZXdpdGg }
++ *
++ * 'encoding' is the encoding method used for binary data, assume name
++ * can be up to 255 chars.
++ * 'size' is the clear text file data length in bytes.
++ * 'enc_ctx' is encoded encryption context, 40 bytes for v2.
++ * 'enc_name' is encoded encrypted name, 256 bytes max.
++ * So on overall, this xattr is at most 727 chars plus terminating '\0'.
++ */
++static int ext4_build_xattr_encdata(struct dentry *dentry,
++                                   struct inode *inode,
++                                   void *buffer, size_t size)
++{
++      char encoded_enc_ctx[BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE) + 1];
++      unsigned char enc_ctx[LLCRYPT_SET_CONTEXT_MAX_SIZE];
++      char encoded_name[BASE64URL_CHARS(NAME_MAX) + 1];
++      struct qstr qstr = QSTR_INIT(NULL, 0);
++      struct inode *parent = NULL;
++      int encoded_enc_ctx_len = 0;
++      int encoded_name_len = 0;
++      char size_str[32];
++      int retval;
++
++      if (!(EXT4_I(inode)->i_flags & EXT4_ENCRYPT_FL)) {
++              retval = -ENODATA;
++              goto out;
++      }
++
++      /* get size */
++      retval = snprintf(size_str, sizeof(size_str), "%llu",
++                        S_ISDIR(inode->i_mode) ? 0 : inode->i_size);
++      if (retval >= sizeof(size_str)) {
++              retval = -ERANGE;
++              goto out;
++      }
++
++      /* get raw name */
++      if (dentry && dentry->d_parent)
++              parent = dentry->d_parent->d_inode;
++
++      retval = ext4_setup_filename(parent, &dentry->d_name, 1, &qstr);
++      if (retval)
++              goto out;
++
++      /* base64url-encode raw name */
++      encoded_name_len = encode(qstr.name, qstr.len, encoded_name,
++                                ENCDATA_ENCODING);
++      if (encoded_name_len == -EINVAL) {
++              retval = -EINVAL;
++              goto out;
++      }
++      encoded_name[encoded_name_len] = '\0';
++
++      if (!buffer) {
++              /* Return exact xattr length we would return if called with
++               * non-NULL buffer.
++               */
++              retval = sizeof(ENCDATA_XATTR_FMT_COMP) - 1 +
++                      sizeof(ENCDATA_ENCODING) - 1 + strlen(size_str) +
++                      BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE) +
++                      encoded_name_len;
++              goto out;
++      }
++
++      /* get encryption context */
++      retval = ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION,
++                              EXT4_XATTR_NAME_ENCRYPTION_CONTEXT,
++                              enc_ctx, sizeof(enc_ctx));
++
++      if (retval < 0)
++              goto out;
++
++      /* base64url-encode encryption context */
++      encoded_enc_ctx_len = encode(enc_ctx, retval, encoded_enc_ctx,
++                                   ENCDATA_ENCODING);
++      if (encoded_enc_ctx_len == -EINVAL) {
++              retval = -EINVAL;
++              goto out;
++      }
++      encoded_enc_ctx[encoded_enc_ctx_len] = '\0';
++
++      /* write EXT4_XATTR_ENCDATA info into buffer */
++      retval = snprintf(buffer, size,
++                        ENCDATA_XATTR_FMT_1 ENCDATA_ENCODING
++                        ENCDATA_XATTR_FMT_2"%s"ENCDATA_XATTR_FMT_3"%s"
++                        ENCDATA_XATTR_FMT_4"%s"ENCDATA_XATTR_FMT_END,
++                        size_str, encoded_enc_ctx,
++                        encoded_name_len ? encoded_name : "");
++      if (retval >= size)
++              retval = -ERANGE;
++
++out:
++      if (qstr.name != dentry->d_name.name)
++              kfree(qstr.name);
++
++      return retval;
++}
++
++static int ext4_process_xattr_encdata(struct inode *inode,
++                                     const void *value, size_t size,
++                                     int flags)
++{
++      char encoded_enc_ctx[BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE) + 1];
++      unsigned char enc_ctx[LLCRYPT_SET_CONTEXT_MAX_SIZE];
++      char encoded_name[BASE64URL_CHARS(NAME_MAX) + 1];
++      char encoding[NAME_MAX + 1];
++      char name[NAME_MAX + 1];
++      loff_t disk_size = 0;
++      char *buffer = NULL;
++      int enc_ctx_len = 0;
++      int name_len = 0;
++      int retval = 0;
++
++      if (EXT4_I(inode)->i_flags & EXT4_ENCRYPT_FL ||
++          !value || flags & XATTR_REPLACE) {
++              retval = -EINVAL;
++              goto out;
++      }
++
++      buffer = kmalloc(size + 1, GFP_NOFS);
++      if (!buffer) {
++              retval = -ENOMEM;
++              goto out;
++      }
++      memcpy(buffer, value, size);
++      buffer[size] = '\0';
++
++      retval = sscanf(buffer, encdata_xattr_fmt,
++                      encoding, &disk_size, encoded_enc_ctx, encoded_name);
++      if (retval < 4) {
++              retval = -EINVAL;
++              goto out;
++      }
++
++      /* get former encryption context: should not exist */
++      retval = ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION,
++                              EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, NULL, 0);
++      if (retval != -ENODATA) {
++              retval = -EINVAL;
++              goto out;
++      }
++
++      if (strlen(encoded_enc_ctx) >
++          BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE)) {
++              retval = -EINVAL;
++              goto out;
++      }
++
++      /* base64url-decode encryption context */
++      retval = decode(encoded_enc_ctx, strlen(encoded_enc_ctx),
++                      enc_ctx, encoding);
++      if (retval < 0) {
++              retval = -EINVAL;
++              goto out;
++      }
++      enc_ctx_len = retval;
++
++      /* set encryption context, this will set encryption flag */
++      retval = ext4_xattr_set(inode, EXT4_XATTR_INDEX_ENCRYPTION,
++                              EXT4_XATTR_NAME_ENCRYPTION_CONTEXT,
++                              enc_ctx, enc_ctx_len, XATTR_CREATE);
++      if (retval < 0)
++              goto out;
++
++      if (disk_size) {
++              /* set size on inode */
++              spin_lock(&inode->i_lock);
++              i_size_write(inode, disk_size);
++              EXT4_I(inode)->i_disksize = disk_size;
++              spin_unlock(&inode->i_lock);
++              mark_inode_dirty(inode);
++      }
++
++      /* put raw encrypted name in EXT4_XATTR_NAME_RAWENCNAME xattr,
++       * for later use, but base64url-decode first
++       */
++      retval = decode(encoded_name, strlen(encoded_name), name, encoding);
++      if (retval < 0) {
++              retval = -EINVAL;
++              goto out;
++      }
++      name_len = retval;
++
++      retval = ext4_xattr_set(inode, EXT4_XATTR_INDEX_LUSTRE,
++                              EXT4_XATTR_NAME_RAWENCNAME,
++                              name, name_len, XATTR_CREATE);
++
++out:
++      kfree(buffer);
++
++      return retval;
++}
++
+ static int
+ ext4_xattr_security_get(struct dentry *dentry, const char *name,
+                      void *buffer, size_t size, int type)
+ {
+       if (strcmp(name, "") == 0)
+               return -EINVAL;
++
++      if (!strncmp(name, EXT4_XATTR_NAME_ENCDATA, strlen(name)))
++              return ext4_build_xattr_encdata(dentry, dentry->d_inode,
++                                                 buffer, size);
++
+       return ext4_xattr_get(dentry->d_inode, EXT4_XATTR_INDEX_SECURITY,
+                             name, buffer, size);
+ }
+@@ -43,6 +235,11 @@ ext4_xattr_security_set(struct dentry
+ {
+       if (strcmp(name, "") == 0)
+               return -EINVAL;
++
++      if (!strncmp(name, EXT4_XATTR_NAME_ENCDATA, strlen(name)))
++              return ext4_process_xattr_encdata(dentry->d_inode, value,
++                                                   size, flags);
++
+       return ext4_xattr_set(dentry->d_inode, EXT4_XATTR_INDEX_SECURITY,
+                             name, value, size, flags);
+ }
+diff -wur a/fs/ext4/super.c b/fs/ext4/super.c
+--- a/fs/ext4/super.c
++++ b/fs/ext4/super.c
+@@ -53,6 +53,7 @@
+ #include "xattr.h"
+ #include "acl.h"
+ #include "mballoc.h"
++#include "encdata.h"
+ #define CREATE_TRACE_POINTS
+ #include <trace/events/ext4.h>
+@@ -6133,7 +6134,8 @@ MODULE_ALIAS_FS("ext4");
+ /* Shared across all ext4 file systems */
+ wait_queue_head_t ext4__ioend_wq[EXT4_WQ_HASH_SZ];
+ struct mutex ext4__aio_mutex[EXT4_WQ_HASH_SZ];
++char encdata_xattr_fmt[NAME_MAX];
+ static int __init ext4_init_fs(void)
+ {
+@@ -6178,6 +6180,12 @@ static int __init ext4_init_fs(void)
+       if (err)
+               goto out;
++      snprintf(encdata_xattr_fmt, sizeof(encdata_xattr_fmt),
++               ENCDATA_XATTR_FMT_1"%%%u[^,]"ENCDATA_XATTR_FMT_2"%%llu"
++               ENCDATA_XATTR_FMT_3"%%%us"ENCDATA_XATTR_FMT_4"%%%us",
++               NAME_MAX, BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE),
++               BASE64URL_CHARS(NAME_MAX));
++
+       return 0;
+ out:
+       unregister_as_ext2();
index 2460066..b3bd8bf 100644 (file)
@@ -97,7 +97,7 @@ diff -wur a/fs/ext4/dir.c b/fs/ext4/dir.c
 diff -wur /dev/null b/fs/ext4/critical_encode.h
 --- /dev/null
 +++ b/fs/ext4/critical_encode.h
-@@ -0,0 +1,74 @@
+@@ -0,0 +1,103 @@
 +/*
 + *  critical_encode.h
 + *
@@ -171,6 +171,35 @@ diff -wur /dev/null b/fs/ext4/critical_encode.h
 +      return (char *)q - dst;
 +}
 +
++static inline int ext4_setup_filename(struct inode *dir,
++                                       const struct qstr *iname,
++                                       int lookup,
++                                       struct qstr *qstr)
++{
++      if (lookup && unlikely(!IS_LUSTRE_MOUNT(dir->i_sb)) &&
++          EXT4_I(dir)->i_flags & EXT4_ENCRYPT_FL &&
++          strnchr(iname->name, iname->len, '=')) {
++              /* Only proceed to critical decode if
++               * iname contains escape char '='.
++               */
++              int len = iname->len;
++              char *buf;
++
++              buf = kmalloc(len, GFP_NOFS);
++              if (!buf)
++                      return -ENOMEM;
++
++              len = critical_decode(iname->name, len, buf);
++              qstr->name = buf;
++              qstr->len = len;
++      } else {
++              qstr->name = iname->name;
++              qstr->len = iname->len;
++      }
++
++      return 0;
++}
++
 +#endif /* _CRITICAL_ENCODE_H */
 diff -wur a/fs/ext4/namei.c b/fs/ext4/namei.c
 --- a/fs/ext4/namei.c
@@ -229,38 +258,24 @@ diff -wur a/fs/ext4/namei.c b/fs/ext4/namei.c
                        /* found a match - just to be sure, do a full check */
                        if (ext4_check_dir_entry(dir, NULL, de, bh, bh->b_data,
                                                 bh->b_size, offset))
-@@ -1588,8 +1589,10 @@ struct buffer_head *__ext4_find_entry
+@@ -1588,8 +1589,9 @@ struct buffer_head *__ext4_find_entry
                                   buffer */
        int num = 0;
        ext4_lblk_t  nblocks;
-+      char *buf = NULL;
        int i, err = 0;
        int namelen;
-+      struct qstr qstr;
++      struct qstr qstr = QSTR_INIT(NULL, 0);
  
        *res_dir = NULL;
        sb = dir->i_sb;
-@@ -1597,6 +1600,24 @@ struct buffer_head *__ext4_find_entry
+@@ -1597,6 +1600,11 @@ struct buffer_head *__ext4_find_entry
        if (namelen > EXT4_NAME_LEN)
                return NULL;
  
-+      if (unlikely(!IS_LUSTRE_MOUNT(dir->i_sb)) &&
-+          EXT4_I(dir)->i_flags & EXT4_ENCRYPT_FL &&
-+          strnchr(d_name->name, d_name->len, '=')) {
-+              /* Only proceed to critical decode if
-+               * iname contains escape char '='.
-+               */
-+              int len = d_name->len;
-+
-+              buf = kmalloc(len, GFP_NOFS);
-+              if (!buf)
-+                      return ERR_PTR(-ENOMEM);
-+
-+              len = critical_decode(d_name->name, len, buf);
-+              qstr.name = buf;
-+              qstr.len = len;
-+              d_name = &qstr;
-+      }
++      err = ext4_setup_filename(dir, d_name, 1, &qstr);
++      if (err)
++              return ERR_PTR(err);
++      d_name = &qstr;
 +
        if (ext4_has_inline_data(dir)) {
                int has_inline_data = 1;
@@ -316,8 +331,8 @@ diff -wur a/fs/ext4/namei.c b/fs/ext4/namei.c
        for (; ra_ptr < ra_max; ra_ptr++)
                brelse(bh_use[ra_ptr]);
 +out_free:
-+      if (buf)
-+              kfree(buf);
++      if (qstr.name != name)
++              kfree(qstr.name);
        return ret;
  }
  EXPORT_SYMBOL(__ext4_find_entry);
index d80ec4c..0140e81 100644 (file)
@@ -1,7 +1,7 @@
 diff -wur /dev/null b/fs/ext4/encdata.h
 --- /dev/null
 +++ b/fs/ext4/encdata.h
-@@ -0,0 +1,16 @@
+@@ -0,0 +1,128 @@
 +/*
 + *  encdata.h
 + *
@@ -16,6 +16,118 @@ diff -wur /dev/null b/fs/ext4/encdata.h
 +#define EXT4_ENCRYPTION_BLOCKBITS 12
 +#define EXT4_ENCRYPTION_UNIT_SIZE ((size_t)1 << EXT4_ENCRYPTION_BLOCKBITS)
 +#define EXT4_ENCRYPTION_MASK      (~(EXT4_ENCRYPTION_UNIT_SIZE - 1))
++#define LLCRYPT_SET_CONTEXT_MAX_SIZE  40
++#define ENCDATA_XATTR_FMT_1 "{ encoding: "
++#define ENCDATA_XATTR_FMT_2 ", size: "
++#define ENCDATA_XATTR_FMT_3 ", enc_ctx: "
++#define ENCDATA_XATTR_FMT_4 ", enc_name: "
++#define ENCDATA_XATTR_FMT_END " }"
++#define ENCDATA_XATTR_FMT_COMP  ENCDATA_XATTR_FMT_1 ENCDATA_XATTR_FMT_2 \
++                              ENCDATA_XATTR_FMT_3 ENCDATA_XATTR_FMT_4 \
++                              ENCDATA_XATTR_FMT_END
++
++extern char encdata_xattr_fmt[NAME_MAX];
++
++/*
++ * base64url encoding, lifted from fs/crypto/fname.c.
++ */
++
++static const char base64url_table[] =
++      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
++
++#define BASE64URL_CHARS(nbytes)       DIV_ROUND_UP((nbytes) * 4, 3)
++
++/**
++ * base64url_encode() - base64url-encode some binary data
++ * @src: the binary data to encode
++ * @srclen: the length of @src in bytes
++ * @dst: (output) the base64url-encoded string.  Not NUL-terminated.
++ *
++ * Encodes data using base64url encoding, i.e. the "Base 64 Encoding with URL
++ * and Filename Safe Alphabet" specified by RFC 4648.  '='-padding isn't used,
++ * as it's unneeded and not required by the RFC.  base64url is used instead of
++ * base64 to avoid the '/' character, which isn't allowed in filenames.
++ *
++ * Return: the length of the resulting base64url-encoded string in bytes.
++ *       This will be equal to LLCRYPT_BASE64URL_CHARS(srclen).
++ */
++static inline int base64url_encode(const u8 *src, int srclen, char *dst)
++{
++      u32 ac = 0;
++      int bits = 0;
++      int i;
++      char *cp = dst;
++
++      for (i = 0; i < srclen; i++) {
++              ac = (ac << 8) | src[i];
++              bits += 8;
++              do {
++                      bits -= 6;
++                      *cp++ = base64url_table[(ac >> bits) & 0x3f];
++              } while (bits >= 6);
++      }
++      if (bits)
++              *cp++ = base64url_table[(ac << (6 - bits)) & 0x3f];
++      return cp - dst;
++}
++
++/**
++ * base64url_decode() - base64url-decode a string
++ * @src: the string to decode.  Doesn't need to be NUL-terminated.
++ * @srclen: the length of @src in bytes
++ * @dst: (output) the decoded binary data
++ *
++ * Decodes a string using base64url encoding, i.e. the "Base 64 Encoding with
++ * URL and Filename Safe Alphabet" specified by RFC 4648.  '='-padding isn't
++ * accepted, nor are non-encoding characters such as whitespace.
++ *
++ * This implementation hasn't been optimized for performance.
++ *
++ * Return: the length of the resulting decoded binary data in bytes,
++ *       or -1 if the string isn't a valid base64url string.
++ */
++static inline int base64url_decode(const char *src, int srclen, u8 *dst)
++{
++      u32 ac = 0;
++      int bits = 0;
++      int i;
++      u8 *bp = dst;
++
++      for (i = 0; i < srclen; i++) {
++              const char *p = strchr(base64url_table, src[i]);
++
++              if (p == NULL || src[i] == 0)
++                      return -1;
++              ac = (ac << 6) | (p - base64url_table);
++              bits += 6;
++              if (bits >= 8) {
++                      bits -= 8;
++                      *bp++ = (u8)(ac >> bits);
++              }
++      }
++      if (ac & ((1 << bits) - 1))
++              return -1;
++      return bp - dst;
++}
++
++/* This version of the code uses base64url encoding for binary data. */
++#define ENCDATA_ENCODING      "base64url"
++
++/* Wrappers to support various encodings. Add new methods in there.
++ */
++static inline int encode(const u8 *src, int srclen, char *dst, char *encoding)
++{
++      if (!strcmp(encoding, "base64url"))
++              return base64url_encode(src, srclen, dst);
++      return -EINVAL;
++}
++
++static inline int decode(const char *src, int srclen, u8 *dst, char *encoding)
++{
++      if (!strcmp(encoding, "base64url"))
++              return base64url_decode(src, srclen, dst);
++      return -EINVAL;
++}
 +
 +#endif /* _ENCDATA_H */
 diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c
@@ -29,7 +141,7 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c
  
  #include <trace/events/ext4.h>
  
-@@ -5769,6 +5770,12 @@ int ext4_getattr(const struct path *p
+@@ -5769,6 +5770,12 @@ int ext4_getattr(const struct path *path
                                  STATX_ATTR_NODUMP);
  
        generic_fillattr(inode, stat);
@@ -42,3 +154,279 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c
        return 0;
  }
  
+diff -wur a/fs/ext4/xattr.h b/fs/ext4/xattr.h
+--- a/fs/ext4/xattr.h
++++ b/fs/ext4/xattr.h
+@@ -126,6 +126,8 @@ extern const struct xattr_handler ext4_x
+ extern const struct xattr_handler ext4_xattr_security_handler;
+ #define EXT4_XATTR_NAME_ENCRYPTION_CONTEXT "c"
++#define EXT4_XATTR_NAME_ENCDATA             "encdata"
++#define EXT4_XATTR_NAME_RAWENCNAME          "rawencname"
+ /*
+  * The EXT4_STATE_NO_EXPAND is overloaded and used for two purposes.
+diff -wur a/fs/ext4/xattr_security.c b/fs/ext4/xattr_security.c
+--- a/fs/ext4/xattr_security.c
++++ b/fs/ext4/xattr_security.c
+@@ -10,13 +10,217 @@
+ #include <linux/slab.h>
+ #include "ext4_jbd2.h"
+ #include "ext4.h"
++#include "critical_encode.h"
++#include "encdata.h"
+ #include "xattr.h"
++/* security.encdata is a virtual xattr containing information related
++ * to encrypted files. It is expressed as ASCII text with a "key: value"
++ * format, and space as field separator. For instance:
++ *
++ *    { encoding: base64url, size: 3012, enc_ctx: YWJjZGVmZ2hpamtsbW
++ *    5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbg, enc_name: ZmlsZXdpdGh2ZX
++ *    J5bG9uZ25hbWVmaWxld2l0aHZlcnlsb25nbmFtZWZpbGV3aXRodmVyeWxvbmdu
++ *    YW1lZmlsZXdpdGg }
++ *
++ * 'encoding' is the encoding method used for binary data, assume name
++ * can be up to 255 chars.
++ * 'size' is the clear text file data length in bytes.
++ * 'enc_ctx' is encoded encryption context, 40 bytes for v2.
++ * 'enc_name' is encoded encrypted name, 256 bytes max.
++ * So on overall, this xattr is at most 727 chars plus terminating '\0'.
++ */
++static int ext4_build_xattr_encdata(struct dentry *dentry,
++                                   struct inode *inode,
++                                   void *buffer, size_t size)
++{
++      char encoded_enc_ctx[BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE) + 1];
++      unsigned char enc_ctx[LLCRYPT_SET_CONTEXT_MAX_SIZE];
++      char encoded_name[BASE64URL_CHARS(NAME_MAX) + 1];
++      struct ext4_filename fname = { 0 };
++      struct inode *parent = NULL;
++      int encoded_enc_ctx_len = 0;
++      int encoded_name_len = 0;
++      char size_str[32];
++      int retval;
++
++      if (!IS_ENCRYPTED(inode)) {
++              retval = -ENODATA;
++              goto out;
++      }
++
++      /* get size */
++      retval = snprintf(size_str, sizeof(size_str), "%llu",
++                        S_ISDIR(inode->i_mode) ? 0 : inode->i_size);
++      if (retval >= sizeof(size_str)) {
++              retval = -ERANGE;
++              goto out;
++      }
++
++      /* get raw name */
++      if (dentry && dentry->d_parent)
++              parent = dentry->d_parent->d_inode;
++
++      retval = ext4_setup_filename(parent, &dentry->d_name, 1, &fname);
++      if (retval)
++              goto out;
++
++      /* base64url-encode raw name */
++      encoded_name_len = encode(fname.disk_name.name, fname.disk_name.len,
++                                encoded_name, ENCDATA_ENCODING);
++      if (encoded_name_len == -EINVAL) {
++              retval = -EINVAL;
++              goto out;
++      }
++      encoded_name[encoded_name_len] = '\0';
++
++      if (!buffer) {
++              /* Return exact xattr length we would return if called with
++               * non-NULL buffer.
++               */
++              retval = sizeof(ENCDATA_XATTR_FMT_COMP) - 1 +
++                      sizeof(ENCDATA_ENCODING) - 1 + strlen(size_str) +
++                      BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE) +
++                      encoded_name_len;
++              goto out;
++      }
++
++      /* get encryption context */
++      retval = ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION,
++                              EXT4_XATTR_NAME_ENCRYPTION_CONTEXT,
++                              enc_ctx, sizeof(enc_ctx));
++
++      if (retval < 0)
++              goto out;
++
++      /* base64url-encode encryption context */
++      encoded_enc_ctx_len = encode(enc_ctx, retval, encoded_enc_ctx,
++                                   ENCDATA_ENCODING);
++      if (encoded_enc_ctx_len == -EINVAL) {
++              retval = -EINVAL;
++              goto out;
++      }
++      encoded_enc_ctx[encoded_enc_ctx_len] = '\0';
++
++      /* write EXT4_XATTR_ENCDATA info into buffer */
++      retval = snprintf(buffer, size,
++                        ENCDATA_XATTR_FMT_1 ENCDATA_ENCODING
++                        ENCDATA_XATTR_FMT_2"%s"ENCDATA_XATTR_FMT_3"%s"
++                        ENCDATA_XATTR_FMT_4"%s"ENCDATA_XATTR_FMT_END,
++                        size_str, encoded_enc_ctx,
++                        encoded_name_len ? encoded_name : "");
++      if (retval >= size)
++              retval = -ERANGE;
++
++out:
++      if (fname.disk_name.name != dentry->d_name.name)
++              kfree(fname.disk_name.name);
++
++      return retval;
++}
++
++static int ext4_process_xattr_encdata(struct inode *inode,
++                                     const void *value, size_t size,
++                                     int flags)
++{
++      char encoded_enc_ctx[BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE) + 1];
++      unsigned char enc_ctx[LLCRYPT_SET_CONTEXT_MAX_SIZE];
++      char encoded_name[BASE64URL_CHARS(NAME_MAX) + 1];
++      char encoding[NAME_MAX + 1];
++      char name[NAME_MAX + 1];
++      loff_t disk_size = 0;
++      char *buffer = NULL;
++      int enc_ctx_len = 0;
++      int name_len = 0;
++      int retval = 0;
++
++      if (IS_ENCRYPTED(inode) || !value || flags & XATTR_REPLACE) {
++              retval = -EINVAL;
++              goto out;
++      }
++
++      buffer = kmalloc(size + 1, GFP_NOFS);
++      if (!buffer) {
++              retval = -ENOMEM;
++              goto out;
++      }
++      memcpy(buffer, value, size);
++      buffer[size] = '\0';
++
++      retval = sscanf(buffer, encdata_xattr_fmt,
++                      encoding, &disk_size, encoded_enc_ctx, encoded_name);
++      if (retval < 4) {
++              retval = -EINVAL;
++              goto out;
++      }
++
++      /* get former encryption context: should not exist */
++      retval = ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION,
++                              EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, NULL, 0);
++      if (retval != -ENODATA) {
++              retval = -EINVAL;
++              goto out;
++      }
++
++      if (strlen(encoded_enc_ctx) >
++          BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE)) {
++              retval = -EINVAL;
++              goto out;
++      }
++
++      /* base64url-decode encryption context */
++      retval = decode(encoded_enc_ctx, strlen(encoded_enc_ctx),
++                      enc_ctx, encoding);
++      if (retval < 0) {
++              retval = -EINVAL;
++              goto out;
++      }
++      enc_ctx_len = retval;
++
++      /* set encryption context, this will set encryption flag */
++      retval = ext4_xattr_set(inode, EXT4_XATTR_INDEX_ENCRYPTION,
++                              EXT4_XATTR_NAME_ENCRYPTION_CONTEXT,
++                              enc_ctx, enc_ctx_len, XATTR_CREATE);
++      if (retval < 0)
++              goto out;
++
++      if (disk_size) {
++              /* set size on inode */
++              spin_lock(&inode->i_lock);
++              i_size_write(inode, disk_size);
++              EXT4_I(inode)->i_disksize = disk_size;
++              spin_unlock(&inode->i_lock);
++              mark_inode_dirty(inode);
++      }
++
++      /* put raw encrypted name in EXT4_XATTR_NAME_RAWENCNAME xattr,
++       * for later use, but base64url-decode first
++       */
++      retval = decode(encoded_name, strlen(encoded_name), name, encoding);
++      if (retval < 0) {
++              retval = -EINVAL;
++              goto out;
++      }
++      name_len = retval;
++
++      retval = ext4_xattr_set(inode, EXT4_XATTR_INDEX_LUSTRE,
++                              EXT4_XATTR_NAME_RAWENCNAME,
++                              name, name_len, XATTR_CREATE);
++
++out:
++      kfree(buffer);
++
++      return retval;
++}
++
+ static int
+ ext4_xattr_security_get(const struct xattr_handler *handler,
+-                      struct dentry *unused, struct inode *inode,
++                         struct dentry *dentry, struct inode *inode,
+                       const char *name, void *buffer, size_t size)
+ {
++      if (!strncmp(name, EXT4_XATTR_NAME_ENCDATA, strlen(name)))
++              return ext4_build_xattr_encdata(dentry, inode, buffer, size);
++
+       return ext4_xattr_get(inode, EXT4_XATTR_INDEX_SECURITY,
+                             name, buffer, size);
+ }
+@@ -27,6 +231,9 @@ ext4_xattr_security_set(const struct xat
+                       const char *name, const void *value,
+                       size_t size, int flags)
+ {
++      if (!strncmp(name, EXT4_XATTR_NAME_ENCDATA, strlen(name)))
++              return ext4_process_xattr_encdata(inode, value, size, flags);
++
+       return ext4_xattr_set(inode, EXT4_XATTR_INDEX_SECURITY,
+                             name, value, size, flags);
+ }
+diff -wur a/fs/ext4/super.c b/fs/ext4/super.c
+--- a/fs/ext4/super.c
++++ b/fs/ext4/super.c
+@@ -53,6 +53,7 @@
+ #include "acl.h"
+ #include "mballoc.h"
+ #include "fsmap.h"
++#include "encdata.h"
+ #define CREATE_TRACE_POINTS
+ #include <trace/events/ext4.h>
+@@ -6133,6 +6134,7 @@ MODULE_ALIAS_FS("ext4");
+ /* Shared across all ext4 file systems */
+ wait_queue_head_t ext4__ioend_wq[EXT4_WQ_HASH_SZ];
++char encdata_xattr_fmt[NAME_MAX];
+ static int __init ext4_init_fs(void)
+ {
+@@ -6178,6 +6180,12 @@ static int __init ext4_init_fs(void)
+       if (err)
+               goto out;
++      snprintf(encdata_xattr_fmt, sizeof(encdata_xattr_fmt),
++               ENCDATA_XATTR_FMT_1"%%%u[^,]"ENCDATA_XATTR_FMT_2"%%llu"
++               ENCDATA_XATTR_FMT_3"%%%us"ENCDATA_XATTR_FMT_4"%%%us",
++               NAME_MAX, BASE64URL_CHARS(LLCRYPT_SET_CONTEXT_MAX_SIZE),
++               BASE64URL_CHARS(NAME_MAX));
++
+       return 0;
+ out:
+       destroy_inodecache();