From fa7c81e81442c8d1ed0ce1c457769f2e144ff2c3 Mon Sep 17 00:00:00 2001 From: Sebastien Buisson Date: Wed, 11 May 2022 15:42:29 +0200 Subject: [PATCH] LU-15848 ldiskfs: escape encrypted file names When a Lustre MDT is mounted as ldiskfs, the names of the encrypted files have to be escaped to avoid breaking the shell. On CentOS 7, the LDISKFS_ENCRYPT_FL flag does not exist. So we add it, and when the target is mounted as ldiskfs (LDISKFS_MOUNT_DIRDATA flag not present) we critical-encode encrypted file names before presentation. And conversely, we critical-decode names upon lookup. On CentOS 8, the LDISKFS_ENCRYPT_FL flag exists. The fscrypt functions from kernel 4.18 are also wired up, but they all refer to -EOPNOTSUPP or equivalent, so they cannot be used to present usable names. So when the target is mounted as ldiskfs (LDISKFS_MOUNT_DIRDATA flag not present) we proceed to critical-encoding of encrypted file names before presentation, and to critical-decoding upon lookup. Signed-off-by: Sebastien Buisson Change-Id: Iaca467eaa233be8142356efa822962953754c2ce Reviewed-on: https://review.whamcloud.com/47309 Reviewed-by: Andreas Dilger Tested-by: jenkins Tested-by: Maloo Reviewed-by: Patrick Farrell Reviewed-by: Oleg Drokin --- ldiskfs/Makefile.in | 2 +- ldiskfs/autoMakefile.am | 1 + .../patches/linux-5.4/ext4-enc-flag.patch | 17 +- .../patches/linux-5.8/ext4-enc-flag.patch | 17 +- .../patches/rhel7.9/ext4-enc-flag.patch | 62 +++- .../patches/rhel7.9/ext4-filename-encode.patch | 332 +++++++++++++++++++ .../patches/rhel8.4/ext4-enc-flag.patch | 17 +- .../patches/rhel8.5/ext4-enc-flag.patch | 17 +- .../patches/rhel8.5/ext4-filename-encode.patch | 368 +++++++++++++++++++++ .../patches/rhel8/ext4-enc-flag.patch | 17 +- .../patches/rhel8/ext4-filename-encode.patch | 368 +++++++++++++++++++++ .../series/ldiskfs-3.10-rhel7.6.series | 1 + .../series/ldiskfs-3.10-rhel7.7.series | 1 + .../series/ldiskfs-3.10-rhel7.8.series | 1 + .../series/ldiskfs-3.10-rhel7.9.series | 1 + .../series/ldiskfs-4.18-rhel8.1.series | 1 + .../series/ldiskfs-4.18-rhel8.2.series | 1 + .../series/ldiskfs-4.18-rhel8.3.series | 1 + .../series/ldiskfs-4.18-rhel8.4.series | 1 + .../series/ldiskfs-4.18-rhel8.5.series | 1 + .../series/ldiskfs-4.18-rhel8.series | 1 + 21 files changed, 1214 insertions(+), 14 deletions(-) create mode 100644 ldiskfs/kernel_patches/patches/rhel7.9/ext4-filename-encode.patch create mode 100644 ldiskfs/kernel_patches/patches/rhel8.5/ext4-filename-encode.patch create mode 100644 ldiskfs/kernel_patches/patches/rhel8/ext4-filename-encode.patch diff --git a/ldiskfs/Makefile.in b/ldiskfs/Makefile.in index a76432b..811b1f4 100644 --- a/ldiskfs/Makefile.in +++ b/ldiskfs/Makefile.in @@ -12,7 +12,7 @@ trace_headers := $(wildcard @LINUX@/include/trace/events/ext4*.h) backfs_sources := $(filter-out %.mod.c %/inode-test.c,$(wildcard @EXT4_SRC_DIR@/*.c)) -new_sources := mmp.c htree_lock.c +new_sources := mmp.c htree_lock.c critical_encode.h new_headers := ldiskfs_patched_sources := $(notdir $(backfs_sources) $(backfs_headers)) $(new_sources) $(new_headers) diff --git a/ldiskfs/autoMakefile.am b/ldiskfs/autoMakefile.am index 45e266d..3bd7483 100644 --- a/ldiskfs/autoMakefile.am +++ b/ldiskfs/autoMakefile.am @@ -60,6 +60,7 @@ endif mkdir -p linux trace/events @echo -n "Replacing 'ext4' with 'ldiskfs':" for i in $(notdir $(backfs_headers) $(backfs_sources)) $(new_sources) ; do \ + [ -f linux-stage/fs/ext4/$$i ] || continue; \ echo -n " $$i" ; \ sed $(strip $(ldiskfs_sed_flags)) \ linux-stage/fs/ext4/$$i > $$i ; \ diff --git a/ldiskfs/kernel_patches/patches/linux-5.4/ext4-enc-flag.patch b/ldiskfs/kernel_patches/patches/linux-5.4/ext4-enc-flag.patch index 6deddd7..7cf59c9 100644 --- a/ldiskfs/kernel_patches/patches/linux-5.4/ext4-enc-flag.patch +++ b/ldiskfs/kernel_patches/patches/linux-5.4/ext4-enc-flag.patch @@ -1,3 +1,16 @@ +diff -wur a/fs/ext4/ext4.h b/fs/ext4/ext4.h +--- a/fs/ext4/ext4.h ++++ b/fs/ext4/ext4.h +@@ -1154,6 +1154,9 @@ struct ext4_inode_info { + #define EXT4_MOUNT_DISCARD 0x40000000 /* Issue DISCARD requests */ + #define EXT4_MOUNT_INIT_INODE_TABLE 0x80000000 /* Initialize uninitialized itables */ + ++/* we know this is a Lustre mount thanks to the DIRDATA flag */ ++#define IS_LUSTRE_MOUNT(sb) test_opt((sb), DIRDATA) ++ + /* + * Mount flags set either automatically (could not be set by mount option) + * based on per file system feature or property or in special cases such as diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -7,7 +20,7 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c new_fl |= S_DAX; - if (flags & EXT4_ENCRYPT_FL) + if (flags & EXT4_ENCRYPT_FL && -+ unlikely(test_opt(inode->i_sb, DIRDATA) != EXT4_MOUNT_DIRDATA)) ++ unlikely(!IS_LUSTRE_MOUNT(inode->i_sb))) new_fl |= S_ENCRYPTED; if (flags & EXT4_CASEFOLD_FL) new_fl |= S_CASEFOLD; @@ -18,7 +31,7 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c stat->attributes |= STATX_ATTR_COMPRESSED; - if (flags & EXT4_ENCRYPT_FL) + if (flags & EXT4_ENCRYPT_FL && -+ unlikely(test_opt(inode->i_sb, DIRDATA) != EXT4_MOUNT_DIRDATA)) ++ unlikely(!IS_LUSTRE_MOUNT(inode->i_sb))) stat->attributes |= STATX_ATTR_ENCRYPTED; if (flags & EXT4_IMMUTABLE_FL) stat->attributes |= STATX_ATTR_IMMUTABLE; diff --git a/ldiskfs/kernel_patches/patches/linux-5.8/ext4-enc-flag.patch b/ldiskfs/kernel_patches/patches/linux-5.8/ext4-enc-flag.patch index d8f2b22..966120a 100644 --- a/ldiskfs/kernel_patches/patches/linux-5.8/ext4-enc-flag.patch +++ b/ldiskfs/kernel_patches/patches/linux-5.8/ext4-enc-flag.patch @@ -1,3 +1,16 @@ +diff -wur a/fs/ext4/ext4.h b/fs/ext4/ext4.h +--- a/fs/ext4/ext4.h ++++ b/fs/ext4/ext4.h +@@ -1154,6 +1154,9 @@ struct ext4_inode_info { + #define EXT4_MOUNT_DISCARD 0x40000000 /* Issue DISCARD requests */ + #define EXT4_MOUNT_INIT_INODE_TABLE 0x80000000 /* Initialize uninitialized itables */ + ++/* we know this is a Lustre mount thanks to the DIRDATA flag */ ++#define IS_LUSTRE_MOUNT(sb) test_opt((sb), DIRDATA) ++ + /* + * Mount flags set either automatically (could not be set by mount option) + * based on per file system feature or property or in special cases such as diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -7,7 +20,7 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c - if (flags & EXT4_ENCRYPT_FL) + if (flags & EXT4_ENCRYPT_FL && -+ unlikely(test_opt(inode->i_sb, DIRDATA) != EXT4_MOUNT_DIRDATA)) ++ unlikely(!IS_LUSTRE_MOUNT(inode->i_sb))) new_fl |= S_ENCRYPTED; if (flags & EXT4_CASEFOLD_FL) new_fl |= S_CASEFOLD; @@ -18,7 +31,7 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c stat->attributes |= STATX_ATTR_COMPRESSED; - if (flags & EXT4_ENCRYPT_FL) + if (flags & EXT4_ENCRYPT_FL && -+ unlikely(test_opt(inode->i_sb, DIRDATA) != EXT4_MOUNT_DIRDATA)) ++ unlikely(!IS_LUSTRE_MOUNT(inode->i_sb))) stat->attributes |= STATX_ATTR_ENCRYPTED; if (flags & EXT4_IMMUTABLE_FL) stat->attributes |= STATX_ATTR_IMMUTABLE; diff --git a/ldiskfs/kernel_patches/patches/rhel7.9/ext4-enc-flag.patch b/ldiskfs/kernel_patches/patches/rhel7.9/ext4-enc-flag.patch index 75814a1..d24ef32 100644 --- a/ldiskfs/kernel_patches/patches/rhel7.9/ext4-enc-flag.patch +++ b/ldiskfs/kernel_patches/patches/rhel7.9/ext4-enc-flag.patch @@ -1,3 +1,16 @@ +diff -wur a/fs/ext4/ext4.h b/fs/ext4/ext4.h +--- a/fs/ext4/ext4.h ++++ b/fs/ext4/ext4.h +@@ -1154,6 +1154,9 @@ struct ext4_inode_info { + #define EXT4_MOUNT_DISCARD 0x40000000 /* Issue DISCARD requests */ + #define EXT4_MOUNT_INIT_INODE_TABLE 0x80000000 /* Initialize uninitialized itables */ + ++/* we know this is a Lustre mount thanks to the DIRDATA flag */ ++#define IS_LUSTRE_MOUNT(sb) test_opt((sb), DIRDATA) ++ + /* + * Mount flags set either automatically (could not be set by mount option) + * based on per file system feature or property or in special cases such as diff -wur a/fs/ext4/xattr.c b/fs/ext4/xattr.c --- a/fs/ext4/xattr.c +++ b/fs/ext4/xattr.c @@ -9,7 +22,19 @@ diff -wur a/fs/ext4/xattr.c b/fs/ext4/xattr.c static int ext4_xattr_list_entries(struct dentry *dentry, struct ext4_xattr_entry *entry, -@@ -1197,6 +1197,7 @@ +@@ -1197,14 +1197,19 @@ + ext4_handle_sync(handle); + } + ++ if (!error && name_index == EXT4_XATTR_INDEX_ENCRYPTION && ++ strcmp(name, "c") == 0) ++ EXT4_I(inode)->i_flags |= EXT4_ENCRYPT_FL; ++ + cleanup: + brelse(is.iloc.bh); + brelse(bs.bh); + if (no_expand == 0) + ext4_clear_inode_state(inode, EXT4_STATE_NO_EXPAND); up_write(&EXT4_I(inode)->xattr_sem); return error; } @@ -24,9 +49,40 @@ diff -wur a/fs/ext4/xattr.h b/fs/ext4/xattr.h extern const struct xattr_handler ext4_xattr_acl_default_handler; extern const struct xattr_handler ext4_xattr_security_handler; -+#define LDISKFS_XATTR_INDEX_ENCRYPTION 9 -+#define LDISKFS_XATTR_NAME_ENCRYPTION_CONTEXT "c" ++#define EXT4_XATTR_INDEX_ENCRYPTION 9 ++#define EXT4_XATTR_NAME_ENCRYPTION_CONTEXT "c" + extern ssize_t ext4_listxattr(struct dentry *, char *, size_t); extern int ext4_xattr_get(struct inode *, int, const char *, void *, size_t); +diff -wur a/fs/ext4/ext4.h b/fs/ext4/ext4.h +--- a/fs/ext4/ext4.h ++++ b/fs/ext4/ext4.h +@@ -381,7 +378,8 @@ struct flex_groups { + #define EXT4_DIRTY_FL 0x00000100 + #define EXT4_COMPRBLK_FL 0x00000200 /* One or more compressed clusters */ + #define EXT4_NOCOMPR_FL 0x00000400 /* Don't compress */ +-#define EXT4_ECOMPR_FL 0x00000800 /* Compression error */ ++/* nb: was previously EXT4_ECOMPR_FL */ ++#define EXT4_ENCRYPT_FL 0x00000800 /* encrypted file */ + /* End compression flags --- maybe not all used */ + #define EXT4_INDEX_FL 0x00001000 /* hash-indexed directory */ + #define EXT4_IMAGIC_FL 0x00002000 /* AFS directory */ +@@ -447,7 +445,7 @@ enum { + EXT4_INODE_DIRTY = 8, + EXT4_INODE_COMPRBLK = 9, /* One or more compressed clusters */ + EXT4_INODE_NOCOMPR = 10, /* Don't compress */ +- EXT4_INODE_ECOMPR = 11, /* Compression error */ ++ EXT4_INODE_ENCRYPT = 11, /* Encrypted file */ + /* End compression flags --- maybe not all used */ + EXT4_INODE_INDEX = 12, /* hash-indexed directory */ + EXT4_INODE_IMAGIC = 13, /* AFS directory */ +@@ -493,7 +491,7 @@ static inline void ext4_check_flag_va + CHECK_FLAG_VALUE(DIRTY); + CHECK_FLAG_VALUE(COMPRBLK); + CHECK_FLAG_VALUE(NOCOMPR); +- CHECK_FLAG_VALUE(ECOMPR); ++ CHECK_FLAG_VALUE(ENCRYPT); + CHECK_FLAG_VALUE(INDEX); + CHECK_FLAG_VALUE(IMAGIC); + CHECK_FLAG_VALUE(JOURNAL_DATA); diff --git a/ldiskfs/kernel_patches/patches/rhel7.9/ext4-filename-encode.patch b/ldiskfs/kernel_patches/patches/rhel7.9/ext4-filename-encode.patch new file mode 100644 index 0000000..2460066 --- /dev/null +++ b/ldiskfs/kernel_patches/patches/rhel7.9/ext4-filename-encode.patch @@ -0,0 +1,332 @@ +diff -wur a/fs/ext4/dir.c b/fs/ext4/dir.c +--- a/fs/ext4/dir.c ++++ b/fs/ext4/dir.c +@@ -28,6 +28,7 @@ + #include + #include "ext4.h" + #include "xattr.h" ++#include "critical_encode.h" + + static int ext4_dx_readdir(struct file *filp, + void *dirent, filldir_t filldir); +@@ -243,12 +244,41 @@ revalidate: + * during the copy operation. + */ + u64 version = filp->f_version; ++ int presented_len = de->name_len; ++ char *buf = de->name; + +- error = filldir(dirent, de->name, +- de->name_len, ++ if (unlikely(!IS_LUSTRE_MOUNT(inode->i_sb)) && ++ EXT4_I(inode)->i_flags & EXT4_ENCRYPT_FL) ++ presented_len = critical_chars(de->name, ++ de->name_len); ++ if (unlikely(de->name_len != presented_len)) { ++ buf = kmalloc(presented_len + 1, ++ GFP_NOFS); ++ if (!buf) { ++ error = -ENOMEM; ++ break; ++ } ++ critical_encode(de->name, ++ de->name_len, buf); ++ 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 (buf[NAME_MAX - 1] == '=') ++ presented_len--; ++ } ++ buf[presented_len] = '\0'; ++ } ++ ++ error = filldir(dirent, buf, ++ presented_len, + filp->f_pos, + le32_to_cpu(de->inode), + get_dtype(sb, de->file_type)); ++ if (unlikely(de->name_len != presented_len)) ++ kfree(buf); + if (error) + break; + if (version != filp->f_version) +@@ -516,10 +542,38 @@ static int call_filldir(struct file *fil + } + curr_pos = hash2pos(filp, fname->hash, fname->minor_hash); + while (fname) { +- error = filldir(dirent, fname->name, +- fname->name_len, curr_pos, ++ int presented_len = fname->name_len; ++ char *buf = fname->name; ++ ++ if (unlikely(!IS_LUSTRE_MOUNT(inode->i_sb)) && ++ EXT4_I(inode)->i_flags & EXT4_ENCRYPT_FL) ++ presented_len = critical_chars(fname->name, ++ fname->name_len); ++ if (unlikely(fname->name_len != presented_len)) { ++ buf = kmalloc(presented_len + 1, GFP_NOFS); ++ if (!buf) { ++ filp->f_pos = curr_pos; ++ info->extra_fname = fname; ++ return -ENOMEM; ++ } ++ critical_encode(fname->name, ++ fname->name_len, buf); ++ 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 (buf[presented_len - 1] == '=') ++ presented_len--; ++ } ++ buf[presented_len] = '\0'; ++ } ++ error = filldir(dirent, buf, ++ presented_len, curr_pos, + fname->inode, + get_dtype(sb, fname->file_type)); ++ if (unlikely(fname->name_len != presented_len)) ++ kfree(buf); + if (error) { + filp->f_pos = curr_pos; + info->extra_fname = fname; +diff -wur /dev/null b/fs/ext4/critical_encode.h +--- /dev/null ++++ b/fs/ext4/critical_encode.h +@@ -0,0 +1,74 @@ ++/* ++ * critical_encode.h ++ * ++ * Copyright (c) 2022 Whamcloud ++ */ ++ ++#ifndef _CRITICAL_ENCODE_H ++#define _CRITICAL_ENCODE_H ++ ++#include ++ ++/* 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; ++} ++ ++#endif /* _CRITICAL_ENCODE_H */ +diff -wur a/fs/ext4/namei.c b/fs/ext4/namei.c +--- a/fs/ext4/namei.c ++++ b/fs/ext4/namei.c +@@ -39,6 +39,7 @@ + + #include "xattr.h" + #include "acl.h" ++#include "critical_encode.h" + + #include + /* +@@ -1494,9 +1494,9 @@ static void dx_insert_block(struct dx_fr + * `de != NULL' is guaranteed by caller. + */ + static inline int ext4_match (int len, const char * const name, +- struct ext4_dir_entry_2 * de) ++ struct ext4_dir_entry_2 * de, int denamelen) + { +- if (len != de->name_len) ++ if (len != denamelen) + return 0; + if (!de->inode) + return 0; +@@ -1516,18 +1516,30 @@ int search_dir(struct buffer_head *bh, + { + struct ext4_dir_entry_2 * de; + char * dlimit; +- int de_len; ++ int de_len, denamelen; + const char *name = d_name->name; + int namelen = d_name->len; ++ bool probablytrunc; + + 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) && ++ EXT4_I(dir)->i_flags & EXT4_ENCRYPT_FL && namelen & 31; + while ((char *) de < dlimit) { + /* this code is executed quadratically often */ + /* do minimal checking `by hand' */ +- ++ denamelen = de->name_len; ++ if (unlikely(probablytrunc) && de->name_len > namelen) ++ /* Adjust name len to look for a partial match. ++ * Since it is binary encrypted names, there ++ * should not be any collision between names. ++ */ ++ denamelen = namelen; + if ((char *) de + namelen <= dlimit && +- ext4_match (namelen, name, de)) { ++ ext4_match(namelen, name, de, denamelen)) { + /* 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 + buffer */ + int num = 0; + ext4_lblk_t nblocks; ++ char *buf = NULL; + int i, err = 0; + int namelen; ++ struct qstr qstr; + + *res_dir = NULL; + sb = dir->i_sb; +@@ -1597,6 +1600,24 @@ 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; ++ } ++ + if (ext4_has_inline_data(dir)) { + int has_inline_data = 1; + ret = ext4_find_inline_entry(dir, d_name, res_dir, +@@ -1604,6 +1624,6 @@ struct buffer_head *__ext4_find_entry + if (has_inline_data) { + if (inlined) + *inlined = 1; +- return ret; ++ goto out_free; + } + } +@@ -1625,12 +1647,18 @@ struct buffer_head *__ext4_find_entry + * return. Otherwise, fall back to doing a search the + * old fashioned way. + */ +- if (err == -ENOENT) +- return NULL; ++ if (err == -ENOENT) { ++ ret = NULL; ++ goto out_free; ++ } +- if (err && err != ERR_BAD_DX_DIR) +- return ERR_PTR(err); ++ if (err && err != ERR_BAD_DX_DIR) { ++ ret = ERR_PTR(err); ++ goto out_free; ++ } +- if (bh) +- return bh; ++ if (bh) { ++ ret = bh; ++ goto out_free; ++ } + dxtrace(printk(KERN_DEBUG "ext4_find_entry: dx failed, " + "falling back\n")); + ext4_htree_safe_relock(lck); +@@ -1667,8 +1698,10 @@ restart: + num++; + bh = ext4_getblk(NULL, dir, b++, 0, &err); + if (unlikely(err)) { +- if (ra_max == 0) +- return ERR_PTR(err); ++ if (ra_max == 0) { ++ ret = ERR_PTR(err); ++ goto out_free; ++ } + break; + } + bh_use[ra_max] = bh; +@@ -1729,6 +1763,9 @@ cleanup_and_exit: + /* Clean up the read-ahead blocks */ + for (; ra_ptr < ra_max; ra_ptr++) + brelse(bh_use[ra_ptr]); ++out_free: ++ if (buf) ++ kfree(buf); + return ret; + } + EXPORT_SYMBOL(__ext4_find_entry); +@@ -2121,7 +2128,7 @@ int ext4_find_dest_de(struct inode *d + if (ext4_check_dir_entry(dir, NULL, de, bh, + buf, buf_size, offset)) + return -EIO; +- if (ext4_match(namelen, name, de)) ++ if (ext4_match(namelen, name, de, de->name_len)) + return -EEXIST; + nlen = EXT4_DIR_REC_LEN(de); + rlen = ext4_rec_len_from_disk(de->rec_len, buf_size); diff --git a/ldiskfs/kernel_patches/patches/rhel8.4/ext4-enc-flag.patch b/ldiskfs/kernel_patches/patches/rhel8.4/ext4-enc-flag.patch index 7256e8f..a6abcab 100644 --- a/ldiskfs/kernel_patches/patches/rhel8.4/ext4-enc-flag.patch +++ b/ldiskfs/kernel_patches/patches/rhel8.4/ext4-enc-flag.patch @@ -1,3 +1,16 @@ +diff -wur a/fs/ext4/ext4.h b/fs/ext4/ext4.h +--- a/fs/ext4/ext4.h ++++ b/fs/ext4/ext4.h +@@ -1154,6 +1154,9 @@ struct ext4_inode_info { + #define EXT4_MOUNT_DISCARD 0x40000000 /* Issue DISCARD requests */ + #define EXT4_MOUNT_INIT_INODE_TABLE 0x80000000 /* Initialize uninitialized itables */ + ++/* we know this is a Lustre mount thanks to the DIRDATA flag */ ++#define IS_LUSTRE_MOUNT(sb) test_opt((sb), DIRDATA) ++ + /* + * Mount flags set either automatically (could not be set by mount option) + * based on per file system feature or property or in special cases such as diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -7,7 +20,7 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c - if (flags & EXT4_ENCRYPT_FL) + if (flags & EXT4_ENCRYPT_FL && -+ unlikely(test_opt(inode->i_sb, DIRDATA) != EXT4_MOUNT_DIRDATA)) ++ unlikely(!IS_LUSTRE_MOUNT(inode->i_sb))) new_fl |= S_ENCRYPTED; inode_set_flags(inode, new_fl, S_SYNC|S_APPEND|S_IMMUTABLE|S_NOATIME|S_DIRSYNC|S_DAX| @@ -18,7 +31,7 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c stat->attributes |= STATX_ATTR_COMPRESSED; - if (flags & EXT4_ENCRYPT_FL) + if (flags & EXT4_ENCRYPT_FL && -+ unlikely(test_opt(inode->i_sb, DIRDATA) != EXT4_MOUNT_DIRDATA)) ++ unlikely(!IS_LUSTRE_MOUNT(inode->i_sb))) stat->attributes |= STATX_ATTR_ENCRYPTED; if (flags & EXT4_IMMUTABLE_FL) stat->attributes |= STATX_ATTR_IMMUTABLE; diff --git a/ldiskfs/kernel_patches/patches/rhel8.5/ext4-enc-flag.patch b/ldiskfs/kernel_patches/patches/rhel8.5/ext4-enc-flag.patch index 811a6ef..2d9d130 100644 --- a/ldiskfs/kernel_patches/patches/rhel8.5/ext4-enc-flag.patch +++ b/ldiskfs/kernel_patches/patches/rhel8.5/ext4-enc-flag.patch @@ -1,3 +1,16 @@ +diff -wur a/fs/ext4/ext4.h b/fs/ext4/ext4.h +--- a/fs/ext4/ext4.h ++++ b/fs/ext4/ext4.h +@@ -1154,6 +1154,9 @@ struct ext4_inode_info { + #define EXT4_MOUNT_DISCARD 0x40000000 /* Issue DISCARD requests */ + #define EXT4_MOUNT_INIT_INODE_TABLE 0x80000000 /* Initialize uninitialized itables */ + ++/* we know this is a Lustre mount thanks to the DIRDATA flag */ ++#define IS_LUSTRE_MOUNT(sb) test_opt((sb), DIRDATA) ++ + /* + * Mount flags set either automatically (could not be set by mount option) + * based on per file system feature or property or in special cases such as diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -7,7 +20,7 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c - if (flags & EXT4_ENCRYPT_FL) + if (flags & EXT4_ENCRYPT_FL && -+ unlikely(test_opt(inode->i_sb, DIRDATA) != EXT4_MOUNT_DIRDATA)) ++ unlikely(!IS_LUSTRE_MOUNT(inode->i_sb))) new_fl |= S_ENCRYPTED; inode_set_flags(inode, new_fl, S_SYNC|S_APPEND|S_IMMUTABLE|S_NOATIME|S_DIRSYNC|S_DAX| @@ -18,7 +31,7 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c stat->attributes |= STATX_ATTR_COMPRESSED; - if (flags & EXT4_ENCRYPT_FL) + if (flags & EXT4_ENCRYPT_FL && -+ unlikely(test_opt(inode->i_sb, DIRDATA) != EXT4_MOUNT_DIRDATA)) ++ unlikely(!IS_LUSTRE_MOUNT(inode->i_sb))) stat->attributes |= STATX_ATTR_ENCRYPTED; if (flags & EXT4_IMMUTABLE_FL) stat->attributes |= STATX_ATTR_IMMUTABLE; diff --git a/ldiskfs/kernel_patches/patches/rhel8.5/ext4-filename-encode.patch b/ldiskfs/kernel_patches/patches/rhel8.5/ext4-filename-encode.patch new file mode 100644 index 0000000..6351670 --- /dev/null +++ b/ldiskfs/kernel_patches/patches/rhel8.5/ext4-filename-encode.patch @@ -0,0 +1,368 @@ +diff -wur /dev/null b/fs/ext4/critical_encode.h +--- /dev/null ++++ b/fs/ext4/critical_encode.h +@@ -0,0 +1,158 @@ ++/* ++ * critical_encode.h ++ * ++ * Copyright (c) 2022 Whamcloud ++ */ ++ ++#ifndef _CRITICAL_ENCODE_H ++#define _CRITICAL_ENCODE_H ++ ++#include ++ ++/* 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_prepare_lookup(struct inode *dir, ++ struct dentry *dentry, ++ unsigned int flags) ++{ ++ if (unlikely(!IS_LUSTRE_MOUNT(dir->i_sb))) ++ return 0; ++ return fscrypt_prepare_lookup(dir, dentry, flags); ++} ++ ++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; ++ return 0; ++} ++ ++#endif /* _CRITICAL_ENCODE_H */ +diff -wur a/fs/ext4/dir.c b/fs/ext4/dir.c +--- a/fs/ext4/dir.c ++++ b/fs/ext4/dir.c +@@ -28,6 +28,7 @@ + #include + #include "ext4.h" + #include "xattr.h" ++#include "critical_encode.h" + + static int ext4_dx_readdir(struct file *, struct dir_context *); + +@@ -144,7 +145,8 @@ static int ext4_readdir(struct file * + return err; + } + +- if (IS_ENCRYPTED(inode)) { ++ /* disable decryption of filename, present only escaped name */ ++ if (0 && IS_ENCRYPTED(inode)) { + err = fscrypt_fname_alloc_buffer(inode, EXT4_NAME_LEN, &fstr); + if (err < 0) + return err; +@@ -258,22 +259,33 @@ static int ext4_readdir(struct file * + 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; + + /* Directory is encrypted */ +- err = fscrypt_fname_disk_to_usr(inode, ++ presented_len = critical_chars(de->name, ++ de->name_len); ++ err = ext4_fname_alloc_buffer(inode, ++ presented_len, ++ &fstr); ++ if (err) ++ goto errout; ++ ++ err = ext4_fname_disk_to_usr(inode, + 0, 0, &de_name, &fstr); + de_name = fstr; +- fstr.len = save_len; +- if (err) ++ if (err) { ++ ext4_fname_free_buffer(&fstr); + goto errout; +- if (!dir_emit(ctx, ++ } ++ 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; + } + } +diff -wur a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c +--- a/fs/ext4/ialloc.c ++++ b/fs/ext4/ialloc.c +@@ -30,6 +30,7 @@ + #include "ext4_jbd2.h" + #include "xattr.h" + #include "acl.h" ++#include "critical_encode.h" + + #include + +diff -wur a/fs/ext4/namei.c b/fs/ext4/namei.c +--- a/fs/ext4/namei.c ++++ b/fs/ext4/namei.c +@@ -40,6 +40,7 @@ + + #include "xattr.h" + #include "acl.h" ++#include "critical_encode.h" + + #include + /* +@@ -1368,22 +1369,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; +@@ -1614,7 +1614,7 @@ static void dx_insert_block(struct dx_fr + * Return: %true if the directory entry matches, otherwise %false. + */ + static inline bool ext4_match(const struct ext4_filename *fname, +- const struct ext4_dir_entry_2 *de) ++ const struct ext4_dir_entry_2 *de, int denamelen) + { + struct fscrypt_name f; + +@@ -1626,7 +1626,7 @@ static inline bool ext4_match(const s + #ifdef CONFIG_EXT4_FS_ENCRYPTION + f.crypto_buf = fname->crypto_buf; + #endif +- return fscrypt_match_name(&f, de->name, de->name_len); ++ return fscrypt_match_name(&f, de->name, denamelen); + } + + /* +@@ -1637,16 +1637,30 @@ int ext4_search_dir(struct buffer_hea + 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) { + /* 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 ((char *) de + de->name_len <= dlimit && +- ext4_match(fname, de)) { ++ ext4_match(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, +@@ -1707,7 +1717,7 @@ struct buffer_head *__ext4_find_entry + if (namelen > EXT4_NAME_LEN) + return NULL; + +- retval = ext4_fname_setup_filename(dir, d_name, 1, &fname); ++ retval = ext4_setup_filename(dir, d_name, 1, &fname); + if (retval == -ENOENT) + return NULL; + if (retval) +@@ -1834,7 +1844,8 @@ cleanup_and_exit: + /* Clean up the read-ahead blocks */ + for (; ra_ptr < ra_max; ra_ptr++) + brelse(bh_use[ra_ptr]); +- ext4_fname_free_filename(&fname); ++ if (fname.disk_name.name != d_name->name) ++ kfree(fname.disk_name.name); + return ret; + } + EXPORT_SYMBOL(__ext4_find_entry); +@@ -1900,7 +1911,7 @@ static struct dentry *ext4_lookup(str + struct buffer_head *bh; + int err; + +- err = fscrypt_prepare_lookup(dir, dentry, flags); ++ err = ext4_prepare_lookup(dir, dentry, flags); + if (err) + return ERR_PTR(err); + +@@ -2206,7 +2221,7 @@ int ext4_find_dest_de(struct inode *d + if (ext4_check_dir_entry(dir, NULL, de, bh, + buf, buf_size, offset)) + return -EFSCORRUPTED; +- if (ext4_match(fname, de)) ++ if (ext4_match(fname, de, de->name_len)) + return -EEXIST; + nlen = EXT4_DIR_ENTRY_LEN(de); + rlen = ext4_rec_len_from_disk(de->rec_len, buf_size); diff --git a/ldiskfs/kernel_patches/patches/rhel8/ext4-enc-flag.patch b/ldiskfs/kernel_patches/patches/rhel8/ext4-enc-flag.patch index 12f4873..e7f8693 100644 --- a/ldiskfs/kernel_patches/patches/rhel8/ext4-enc-flag.patch +++ b/ldiskfs/kernel_patches/patches/rhel8/ext4-enc-flag.patch @@ -1,3 +1,16 @@ +diff -wur a/fs/ext4/ext4.h b/fs/ext4/ext4.h +--- a/fs/ext4/ext4.h ++++ b/fs/ext4/ext4.h +@@ -1154,6 +1154,9 @@ struct ext4_inode_info { + #define EXT4_MOUNT_DISCARD 0x40000000 /* Issue DISCARD requests */ + #define EXT4_MOUNT_INIT_INODE_TABLE 0x80000000 /* Initialize uninitialized itables */ + ++/* we know this is a Lustre mount thanks to the DIRDATA flag */ ++#define IS_LUSTRE_MOUNT(sb) test_opt((sb), DIRDATA) ++ + /* + * Mount flags set either automatically (could not be set by mount option) + * based on per file system feature or property or in special cases such as diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -7,7 +20,7 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c new_fl |= S_DAX; - if (flags & EXT4_ENCRYPT_FL) + if (flags & EXT4_ENCRYPT_FL && -+ unlikely(test_opt(inode->i_sb, DIRDATA) != EXT4_MOUNT_DIRDATA)) ++ unlikely(!IS_LUSTRE_MOUNT(inode->i_sb))) new_fl |= S_ENCRYPTED; inode_set_flags(inode, new_fl, S_SYNC|S_APPEND|S_IMMUTABLE|S_NOATIME|S_DIRSYNC|S_DAX| @@ -18,7 +31,7 @@ diff -wur a/fs/ext4/inode.c b/fs/ext4/inode.c stat->attributes |= STATX_ATTR_COMPRESSED; - if (flags & EXT4_ENCRYPT_FL) + if (flags & EXT4_ENCRYPT_FL && -+ unlikely(test_opt(inode->i_sb, DIRDATA) != EXT4_MOUNT_DIRDATA)) ++ unlikely(!IS_LUSTRE_MOUNT(inode->i_sb))) stat->attributes |= STATX_ATTR_ENCRYPTED; if (flags & EXT4_IMMUTABLE_FL) stat->attributes |= STATX_ATTR_IMMUTABLE; diff --git a/ldiskfs/kernel_patches/patches/rhel8/ext4-filename-encode.patch b/ldiskfs/kernel_patches/patches/rhel8/ext4-filename-encode.patch new file mode 100644 index 0000000..728ffa8 --- /dev/null +++ b/ldiskfs/kernel_patches/patches/rhel8/ext4-filename-encode.patch @@ -0,0 +1,368 @@ +diff -wur /dev/null b/fs/ext4/critical_encode.h +--- /dev/null ++++ b/fs/ext4/critical_encode.h +@@ -0,0 +1,158 @@ ++/* ++ * critical_encode.h ++ * ++ * Copyright (c) 2022 Whamcloud ++ */ ++ ++#ifndef _CRITICAL_ENCODE_H ++#define _CRITICAL_ENCODE_H ++ ++#include ++ ++/* 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_prepare_lookup(struct inode *dir, ++ struct dentry *dentry, ++ unsigned int flags) ++{ ++ if (unlikely(!IS_LUSTRE_MOUNT(dir->i_sb))) ++ return 0; ++ return fscrypt_prepare_lookup(dir, dentry, flags); ++} ++ ++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; ++ return 0; ++} ++ ++#endif /* _CRITICAL_ENCODE_H */ +diff -wur a/fs/ext4/dir.c b/fs/ext4/dir.c +--- a/fs/ext4/dir.c ++++ b/fs/ext4/dir.c +@@ -28,6 +28,7 @@ + #include + #include "ext4.h" + #include "xattr.h" ++#include "critical_encode.h" + + static int ext4_dx_readdir(struct file *, struct dir_context *); + +@@ -144,7 +145,8 @@ static int ext4_readdir(struct file * + return err; + } + +- if (IS_ENCRYPTED(inode)) { ++ /* disable decryption of filename, present only escaped name */ ++ if (0 && IS_ENCRYPTED(inode)) { + err = fscrypt_fname_alloc_buffer(inode, EXT4_NAME_LEN, &fstr); + if (err < 0) + return err; +@@ -258,22 +259,33 @@ static int ext4_readdir(struct file * + 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; + + /* Directory is encrypted */ +- err = fscrypt_fname_disk_to_usr(inode, ++ presented_len = critical_chars(de->name, ++ de->name_len); ++ err = ext4_fname_alloc_buffer(inode, ++ presented_len, ++ &fstr); ++ if (err) ++ goto errout; ++ ++ err = ext4_fname_disk_to_usr(inode, + 0, 0, &de_name, &fstr); + de_name = fstr; +- fstr.len = save_len; +- if (err) ++ if (err) { ++ ext4_fname_free_buffer(&fstr); + goto errout; +- if (!dir_emit(ctx, ++ } ++ 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; + } + } +diff -wur a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c +--- a/fs/ext4/ialloc.c ++++ b/fs/ext4/ialloc.c +@@ -30,6 +30,7 @@ + #include "ext4_jbd2.h" + #include "xattr.h" + #include "acl.h" ++#include "critical_encode.h" + + #include + +diff -wur a/fs/ext4/namei.c b/fs/ext4/namei.c +--- a/fs/ext4/namei.c ++++ b/fs/ext4/namei.c +@@ -40,6 +40,7 @@ + + #include "xattr.h" + #include "acl.h" ++#include "critical_encode.h" + + #include + /* +@@ -1368,22 +1369,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; +@@ -1614,7 +1614,7 @@ static void dx_insert_block(struct dx_fr + * Return: %true if the directory entry matches, otherwise %false. + */ + static inline bool ext4_match(const struct ext4_filename *fname, +- const struct ext4_dir_entry_2 *de) ++ const struct ext4_dir_entry_2 *de, int denamelen) + { + struct fscrypt_name f; + +@@ -1626,7 +1626,7 @@ static inline bool ext4_match(const s + #ifdef CONFIG_EXT4_FS_ENCRYPTION + f.crypto_buf = fname->crypto_buf; + #endif +- return fscrypt_match_name(&f, de->name, de->name_len); ++ return fscrypt_match_name(&f, de->name, denamelen); + } + + /* +@@ -1637,16 +1637,30 @@ int ext4_search_dir(struct buffer_hea + 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) { + /* 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 ((char *) de + de->name_len <= dlimit && +- ext4_match(fname, de)) { ++ ext4_match(fname, de, denamelen)) { + /* found a match - just to be sure, do + * a full check */ + if (ext4_check_dir_entry(dir, NULL, de, bh, bh->b_data, +@@ -1707,7 +1717,7 @@ struct buffer_head *__ext4_find_entry + if (namelen > EXT4_NAME_LEN) + return NULL; + +- retval = ext4_fname_setup_filename(dir, d_name, 1, &fname); ++ retval = ext4_setup_filename(dir, d_name, 1, &fname); + if (retval == -ENOENT) + return NULL; + if (retval) +@@ -1834,7 +1844,8 @@ cleanup_and_exit: + /* Clean up the read-ahead blocks */ + for (; ra_ptr < ra_max; ra_ptr++) + brelse(bh_use[ra_ptr]); +- ext4_fname_free_filename(&fname); ++ if (fname.disk_name.name != d_name->name) ++ kfree(fname.disk_name.name); + return ret; + } + EXPORT_SYMBOL(__ext4_find_entry); +@@ -1900,7 +1911,7 @@ static struct dentry *ext4_lookup(str + struct buffer_head *bh; + int err; + +- err = fscrypt_prepare_lookup(dir, dentry, flags); ++ err = ext4_prepare_lookup(dir, dentry, flags); + if (err) + return ERR_PTR(err); + +@@ -2206,7 +2221,7 @@ int ext4_find_dest_de(struct inode *d + if (ext4_check_dir_entry(dir, NULL, de, bh, + buf, buf_size, offset)) + return -EFSCORRUPTED; +- if (ext4_match(fname, de)) ++ if (ext4_match(fname, de, de->name_len)) + return -EEXIST; + nlen = EXT4_DIR_ENTRY_LEN(de); + rlen = ext4_rec_len_from_disk(de->rec_len, buf_size); diff --git a/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.6.series b/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.6.series index 19caf50..1e5079f 100644 --- a/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.6.series +++ b/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.6.series @@ -52,3 +52,4 @@ rhel7.6/ext4-dquot-commit-speedup.patch rhel7.7/ext4-ialloc-uid-gid-and-pass-owner-down.patch rhel7.6/ext4-projid-xattrs.patch rhel7.9/ext4-enc-flag.patch +rhel7.9/ext4-filename-encode.patch diff --git a/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.7.series b/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.7.series index 73e01c9..ebf4be8 100644 --- a/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.7.series +++ b/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.7.series @@ -52,3 +52,4 @@ rhel7.6/ext4-dquot-commit-speedup.patch rhel7.7/ext4-ialloc-uid-gid-and-pass-owner-down.patch rhel7.6/ext4-projid-xattrs.patch rhel7.9/ext4-enc-flag.patch +rhel7.9/ext4-filename-encode.patch diff --git a/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.8.series b/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.8.series index 3415561..28726f8 100644 --- a/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.8.series +++ b/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.8.series @@ -45,3 +45,4 @@ rhel7.6/ext4-dquot-commit-speedup.patch rhel7.7/ext4-ialloc-uid-gid-and-pass-owner-down.patch rhel7.6/ext4-projid-xattrs.patch rhel7.9/ext4-enc-flag.patch +rhel7.9/ext4-filename-encode.patch diff --git a/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.9.series b/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.9.series index f6d1ddf..a07d728 100644 --- a/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.9.series +++ b/ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.9.series @@ -45,3 +45,4 @@ rhel7.6/ext4-dquot-commit-speedup.patch rhel7.7/ext4-ialloc-uid-gid-and-pass-owner-down.patch rhel7.6/ext4-projid-xattrs.patch rhel7.9/ext4-enc-flag.patch +rhel7.9/ext4-filename-encode.patch diff --git a/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.1.series b/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.1.series index dd0475e..e1fa7c4 100644 --- a/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.1.series +++ b/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.1.series @@ -30,3 +30,4 @@ rhel8/ext4-ialloc-uid-gid-and-pass-owner-down.patch base/ext4-projid-xattrs.patch rhel8/linux-5.4/ext4-enc-flag.patch base/ext4-delayed-iput.patch +rhel8/ext4-filename-encode.patch diff --git a/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.2.series b/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.2.series index e5911c2..f884a28 100644 --- a/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.2.series +++ b/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.2.series @@ -30,3 +30,4 @@ rhel8/ext4-ialloc-uid-gid-and-pass-owner-down.patch base/ext4-projid-xattrs.patch rhel8/ext4-enc-flag.patch base/ext4-delayed-iput.patch +rhel8/ext4-filename-encode.patch diff --git a/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.3.series b/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.3.series index 8092cd9..fd3d026 100644 --- a/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.3.series +++ b/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.3.series @@ -30,3 +30,4 @@ rhel8/ext4-ialloc-uid-gid-and-pass-owner-down.patch base/ext4-projid-xattrs.patch rhel8/ext4-enc-flag.patch base/ext4-delayed-iput.patch +rhel8/ext4-filename-encode.patch diff --git a/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.4.series b/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.4.series index 97a3200..6856d42 100644 --- a/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.4.series +++ b/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.4.series @@ -30,3 +30,4 @@ rhel8/ext4-ialloc-uid-gid-and-pass-owner-down.patch base/ext4-projid-xattrs.patch rhel8.4/ext4-enc-flag.patch base/ext4-delayed-iput.patch +rhel8.5/ext4-filename-encode.patch diff --git a/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.5.series b/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.5.series index 0a5a156..a40f4e1 100644 --- a/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.5.series +++ b/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.5.series @@ -30,3 +30,4 @@ rhel8/ext4-ialloc-uid-gid-and-pass-owner-down.patch base/ext4-projid-xattrs.patch rhel8.5/ext4-enc-flag.patch base/ext4-delayed-iput.patch +rhel8.5/ext4-filename-encode.patch diff --git a/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.series b/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.series index 2bfffb9..56a2ca3 100644 --- a/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.series +++ b/ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.series @@ -32,3 +32,4 @@ rhel8/ext4-ialloc-uid-gid-and-pass-owner-down.patch ubuntu18/ext4-projid-xattrs.patch rhel8/ext4-enc-flag.patch base/ext4-delayed-iput.patch +rhel8/ext4-filename-encode.patch -- 1.8.3.1