--- /dev/null
+From d0a722cb8fb886380e24e8261e8efca09a3262d6 Mon Sep 17 00:00:00 2001
+From: Sebastien Buisson <sbuisson@ddn.com>
+Date: Tue, 20 Dec 2022 15:40:52 +0100
+Subject: [PATCH] LU-16374 ldiskfs: implement security.encdata xattr
+
+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>
+---
+
+===================================================================
+--- /dev/null
++++ linux-stage/fs/ext4/critical_encode.h
+@@ -0,0 +1,170 @@
++/*
++ * critical_encode.h
++ *
++ * Copyright (c) 2022 Whamcloud
++ */
++
++#ifndef _CRITICAL_ENCODE_H
++#define _CRITICAL_ENCODE_H
++
++#include <linux/ctype.h>
++
++/* Encoding/decoding routines inspired from yEnc principles.
++ * We just take care of a few critical characters:
++ * NULL, LF, CR, /, DEL and =.
++ * If such a char is found, it is replaced with '=' followed by
++ * the char value + 64.
++ * All other chars are left untouched.
++ * Efficiency of this encoding depends on the occurences of the
++ * critical chars, but statistically on binary data it can be much higher
++ * than base64 for instance.
++ */
++static inline int critical_encode(const u8 *src, int len, char *dst)
++{
++ u8 *p = (u8 *)src, *q = dst;
++
++ while (p - src < len) {
++ /* escape NULL, LF, CR, /, DEL and = */
++ if (unlikely(*p == 0x0 || *p == 0xA || *p == 0xD ||
++ *p == '/' || *p == 0x7F || *p == '=')) {
++ *(q++) = '=';
++ *(q++) = *(p++) + 64;
++ } else {
++ *(q++) = *(p++);
++ }
++ }
++
++ return (char *)q - dst;
++}
++
++/* returns the number of chars encoding would produce */
++static inline int critical_chars(const u8 *src, int len)
++{
++ u8 *p = (u8 *)src;
++ int newlen = len;
++
++ while (p - src < len) {
++ /* NULL, LF, CR, /, DEL and = cost an additional '=' */
++ if (unlikely(*p == 0x0 || *p == 0xA || *p == 0xD ||
++ *p == '/' || *p == 0x7F || *p == '='))
++ newlen++;
++ p++;
++ }
++
++ return newlen;
++}
++
++/* decoding routine - returns the number of chars in output */
++static inline int critical_decode(const u8 *src, int len, char *dst)
++{
++ u8 *p = (u8 *)src, *q = dst;
++
++ while (p - src < len) {
++ if (unlikely(*p == '=')) {
++ *(q++) = *(++p) - 64;
++ p++;
++ } else {
++ *(q++) = *(p++);
++ }
++ }
++
++ return (char *)q - dst;
++}
++
++#define fscrypt_get_encryption_info(inode) \
++ (unlikely(!IS_LUSTRE_MOUNT(inode->i_sb)) ? 0 : -EOPNOTSUPP)
++
++static inline int ext4_has_permitted_context(struct inode *parent,
++ struct inode *child)
++{
++ if (unlikely(!IS_LUSTRE_MOUNT(parent->i_sb)))
++ return 1;
++ return fscrypt_has_permitted_context(parent, child);
++}
++
++struct ext4_filename;
++
++static inline int ext4_prepare_readdir(struct inode *dir)
++{
++ if (unlikely(!IS_LUSTRE_MOUNT(dir->i_sb)))
++ return 0;
++ return fscrypt_prepare_readdir(dir);
++}
++
++static inline int ext4_fname_alloc_buffer(const struct inode *inode,
++ u32 max_encrypted_len,
++ struct fscrypt_str *crypto_str)
++{
++ crypto_str->name = kmalloc(max_encrypted_len + 1, GFP_NOFS);
++ if (!crypto_str->name)
++ return -ENOMEM;
++ crypto_str->len = max_encrypted_len;
++ return 0;
++}
++
++static inline void ext4_fname_free_buffer(struct fscrypt_str *crypto_str)
++{
++ if (!crypto_str)
++ return;
++ kfree(crypto_str->name);
++ crypto_str->name = NULL;
++}
++
++static inline int ext4_fname_disk_to_usr(struct inode *inode,
++ u32 hash, u32 minor_hash,
++ const struct fscrypt_str *iname,
++ struct fscrypt_str *oname)
++{
++ int presented_len;
++
++ presented_len = critical_encode(iname->name, iname->len, oname->name);
++ if (presented_len > NAME_MAX) {
++ /* truncate at NAME_MAX,
++ * or NAME_MAX-1 if name ends with '=' to avoid decoding issue
++ */
++ presented_len = NAME_MAX;
++ if (oname->name[presented_len - 1] == '=')
++ presented_len--;
++ oname->len = presented_len;
++ }
++ oname->name[presented_len] = '\0';
++
++ return 0;
++}
++
++static inline int ext4_setup_filename(struct inode *dir,
++ const struct qstr *iname,
++ int lookup,
++ struct ext4_filename *fname)
++{
++ fname->usr_fname = iname;
++
++ if (lookup && IS_ENCRYPTED(dir) &&
++ unlikely(!IS_LUSTRE_MOUNT(dir->i_sb) &&
++ 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);
++ fname->disk_name.name = (unsigned char *)buf;
++ fname->disk_name.len = len;
++ return 0;
++ }
++
++ fname->disk_name.name = (unsigned char *) iname->name;
++ fname->disk_name.len = iname->len;
++
++#ifdef CONFIG_UNICODE
++ ext4_fname_setup_ci_filename(dir, iname, fname);
++#endif
++ return 0;
++}
++
++#endif /* _CRITICAL_ENCODE_H */
+Index: linux-stage/fs/ext4/dir.c
+===================================================================
+--- linux-stage.orig/fs/ext4/dir.c
++++ linux-stage/fs/ext4/dir.c
+@@ -29,6 +29,7 @@
+ #include <linux/unicode.h>
+ #include "ext4.h"
+ #include "xattr.h"
++#include "critical_encode.h"
+
+ static int ext4_dx_readdir(struct file *, struct dir_context *);
+
+@@ -134,7 +135,7 @@ static int ext4_readdir(struct file *fil
+ struct buffer_head *bh = NULL;
+ struct fscrypt_str fstr = FSTR_INIT(NULL, 0);
+
+- err = fscrypt_prepare_readdir(inode);
++ err = ext4_prepare_readdir(inode);
+ if (err)
+ return err;
+
+@@ -161,7 +162,8 @@ static int ext4_readdir(struct file *fil
+ return err;
+ }
+
+- if (IS_ENCRYPTED(inode)) {
++ /* disable decryption of filename, present only escaped name */
++ if (0 && IS_ENCRYPTED(inode)) {
+ err = fscrypt_fname_alloc_buffer(EXT4_NAME_LEN, &fstr);
+ if (err < 0)
+ return err;
+@@ -275,10 +277,10 @@ static int ext4_readdir(struct file *fil
+ get_dtype(sb, de->file_type)))
+ goto done;
+ } else {
+- int save_len = fstr.len;
+ struct fscrypt_str de_name =
+ FSTR_INIT(de->name,
+ de->name_len);
++ int presented_len;
+ u32 hash;
+ u32 minor_hash;
+
+@@ -291,16 +293,27 @@ static int ext4_readdir(struct file *fil
+ }
+
+ /* Directory is encrypted */
+- err = fscrypt_fname_disk_to_usr(inode,
+- hash, minor_hash, &de_name, &fstr);
+- de_name = fstr;
+- fstr.len = save_len;
++ presented_len = critical_chars(de->name,
++ de->name_len);
++ err = ext4_fname_alloc_buffer(inode,
++ presented_len,
++ &fstr);
+ if (err)
+ goto errout;
+- if (!dir_emit(ctx,
++
++ err = ext4_fname_disk_to_usr(inode,
++ 0, 0, &de_name, &fstr);
++ de_name = fstr;
++ if (err) {
++ ext4_fname_free_buffer(&fstr);
++ goto errout;
++ }
++ err = dir_emit(ctx,
+ de_name.name, de_name.len,
+ le32_to_cpu(de->inode),
+- get_dtype(sb, de->file_type)))
++ get_dtype(sb, de->file_type));
++ ext4_fname_free_buffer(&fstr);
++ if (!err)
+ goto done;
+ }
+ }
+Index: linux-stage/fs/ext4/ialloc.c
+===================================================================
+--- linux-stage.orig/fs/ext4/ialloc.c
++++ linux-stage/fs/ext4/ialloc.c
+@@ -30,6 +30,7 @@
+ #include "ext4_jbd2.h"
+ #include "xattr.h"
+ #include "acl.h"
++#include "critical_encode.h"
+
+ #include <trace/events/ext4.h>
+
+Index: linux-stage/fs/ext4/namei.c
+===================================================================
+--- linux-stage.orig/fs/ext4/namei.c
++++ linux-stage/fs/ext4/namei.c
+@@ -41,6 +41,7 @@
+
+ #include "xattr.h"
+ #include "acl.h"
++#include "critical_encode.h"
+
+ #include <trace/events/ext4.h>
+ /*
+@@ -1442,7 +1443,7 @@ static int htree_dirblock_to_tree(struct
+ ext4_dir_rec_len(0,
+ csum ? NULL : dir));
+ /* Check if the directory is encrypted */
+- if (IS_ENCRYPTED(dir)) {
++ if (0 && IS_ENCRYPTED(dir)) {
+ err = fscrypt_prepare_readdir(dir);
+ if (err < 0) {
+ brelse(bh);
+@@ -1493,22 +1494,31 @@ static int htree_dirblock_to_tree(struct
+ hinfo->hash, hinfo->minor_hash, de,
+ &tmp_str);
+ } else {
+- int save_len = fname_crypto_str.len;
+ struct fscrypt_str de_name = FSTR_INIT(de->name,
+ de->name_len);
++ int presented_len;
+
+ /* Directory is encrypted */
+- err = fscrypt_fname_disk_to_usr(dir, hinfo->hash,
++ presented_len = critical_chars(de->name, de->name_len);
++ err = ext4_fname_alloc_buffer(dir, presented_len,
++ &fname_crypto_str);
++ if (err) {
++ count = err;
++ goto errout;
++ }
++
++ err = ext4_fname_disk_to_usr(dir, hinfo->hash,
+ hinfo->minor_hash, &de_name,
+ &fname_crypto_str);
+ if (err) {
++ ext4_fname_free_buffer(&fname_crypto_str);
+ count = err;
+ goto errout;
+ }
+ err = ext4_htree_store_dirent(dir_file,
+ hinfo->hash, hinfo->minor_hash, de,
+ &fname_crypto_str);
+- fname_crypto_str.len = save_len;
++ ext4_fname_free_buffer(&fname_crypto_str);
+ }
+ if (err != 0) {
+ count = err;
+@@ -1787,7 +1797,7 @@ int ext4_fname_setup_ci_filename(struct
+ */
+ static bool ext4_match(struct inode *parent,
+ const struct ext4_filename *fname,
+- struct ext4_dir_entry_2 *de)
++ struct ext4_dir_entry_2 *de, int denamelen)
+ {
+ struct fscrypt_name f;
+
+@@ -1829,7 +1839,7 @@ static bool ext4_match(struct inode *par
+ }
+ #endif
+
+- return fscrypt_match_name(&f, de->name, de->name_len);
++ return fscrypt_match_name(&f, de->name, denamelen);
+ }
+
+ /*
+@@ -1840,16 +1850,30 @@ int ext4_search_dir(struct buffer_head *
+ unsigned int offset, struct ext4_dir_entry_2 **res_dir)
+ {
+ struct ext4_dir_entry_2 * de;
++ bool probablytrunc;
+ char * dlimit;
+- int de_len;
++ int de_len, denamelen;
+
+ de = (struct ext4_dir_entry_2 *)search_buf;
+ dlimit = search_buf + buf_size;
++ /* fname is probably truncated if it is the decoded representation of
++ * an encrypted filename not aligned on a 32-byte boundary
++ */
++ probablytrunc = !IS_LUSTRE_MOUNT(dir->i_sb) && IS_ENCRYPTED(dir) &&
++ fname->disk_name.len & 31;
+ while ((char *) de < dlimit - EXT4_BASE_DIR_LEN) {
+ /* this code is executed quadratically often */
+ /* do minimal checking `by hand' */
++ denamelen = de->name_len;
++ if (unlikely(probablytrunc) &&
++ de->name_len > fname->disk_name.len)
++ /* Adjust name len to look for a partial match.
++ * Since it is binary encrypted names, there
++ * should not be any collision between names.
++ */
++ denamelen = fname->disk_name.len;
+ if (de->name + de->name_len <= dlimit &&
+- ext4_match(dir, fname, de)) {
++ ext4_match(dir, fname, de, denamelen)) {
+ /* found a match - just to be sure, do
+ * a full check */
+ if (ext4_check_dir_entry(dir, NULL, de, bh, search_buf,
+@@ -2052,7 +2076,7 @@ struct buffer_head *ext4_find_entry_lock
+ struct ext4_filename fname;
+ struct buffer_head *bh;
+
+- err = ext4_fname_setup_filename(dir, d_name, 1, &fname);
++ err = ext4_setup_filename(dir, d_name, 1, &fname);
+ if (err == -ENOENT)
+ return NULL;
+ if (err)
+@@ -2060,7 +2084,9 @@ struct buffer_head *ext4_find_entry_lock
+
+ bh = __ext4_find_entry(dir, &fname, res_dir, inlined, lck);
+
+- ext4_fname_free_filename(&fname);
++ if (fname.disk_name.name != d_name->name)
++ kfree(fname.disk_name.name);
++
+ return bh;
+ }
+
+@@ -2074,7 +2100,7 @@ static struct buffer_head *ext4_lookup_e
+ struct ext4_filename fname;
+ struct buffer_head *bh;
+
+- err = ext4_fname_prepare_lookup(dir, dentry, &fname);
++ err = ext4_setup_filename(dir, &dentry->d_name, 1, &fname);
+ if (err == -ENOENT)
+ return NULL;
+ if (err)
+@@ -2082,7 +2108,9 @@ static struct buffer_head *ext4_lookup_e
+
+ bh = __ext4_find_entry(dir, &fname, res_dir, NULL, NULL);
+
+- ext4_fname_free_filename(&fname);
++ if (fname.disk_name.name != dentry->d_name.name)
++ kfree(fname.disk_name.name);
++
+ return bh;
+ }
+
+@@ -2174,7 +2202,7 @@ static struct dentry *ext4_lookup(struct
+ }
+ if (!IS_ERR(inode) && IS_ENCRYPTED(dir) &&
+ (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)) &&
+- !fscrypt_has_permitted_context(dir, inode)) {
++ !ext4_has_permitted_context(dir, inode)) {
+ ext4_warning(inode->i_sb,
+ "Inconsistent encryption contexts: %lu/%lu",
+ dir->i_ino, inode->i_ino);
+@@ -2466,7 +2494,7 @@ int ext4_find_dest_de(struct inode *dir,
+ if (ext4_check_dir_entry(dir, NULL, de, bh,
+ buf, buf_size, offset))
+ return -EFSCORRUPTED;
+- if (ext4_match(dir, fname, de))
++ if (ext4_match(dir, fname, de, de->name_len))
+ return -EEXIST;
+ nlen = EXT4_DIR_ENTRY_LEN(de, dir);
+ rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);