Whamcloud - gitweb
LU-15848 ldiskfs: escape encrypted file names 09/47309/8
authorSebastien Buisson <sbuisson@ddn.com>
Wed, 11 May 2022 13:42:29 +0000 (15:42 +0200)
committerOleg Drokin <green@whamcloud.com>
Fri, 27 May 2022 20:39:11 +0000 (20:39 +0000)
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 <sbuisson@ddn.com>
Change-Id: Iaca467eaa233be8142356efa822962953754c2ce
Reviewed-on: https://review.whamcloud.com/47309
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Patrick Farrell <pfarrell@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
21 files changed:
ldiskfs/Makefile.in
ldiskfs/autoMakefile.am
ldiskfs/kernel_patches/patches/linux-5.4/ext4-enc-flag.patch
ldiskfs/kernel_patches/patches/linux-5.8/ext4-enc-flag.patch
ldiskfs/kernel_patches/patches/rhel7.9/ext4-enc-flag.patch
ldiskfs/kernel_patches/patches/rhel7.9/ext4-filename-encode.patch [new file with mode: 0644]
ldiskfs/kernel_patches/patches/rhel8.4/ext4-enc-flag.patch
ldiskfs/kernel_patches/patches/rhel8.5/ext4-enc-flag.patch
ldiskfs/kernel_patches/patches/rhel8.5/ext4-filename-encode.patch [new file with mode: 0644]
ldiskfs/kernel_patches/patches/rhel8/ext4-enc-flag.patch
ldiskfs/kernel_patches/patches/rhel8/ext4-filename-encode.patch [new file with mode: 0644]
ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.6.series
ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.7.series
ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.8.series
ldiskfs/kernel_patches/series/ldiskfs-3.10-rhel7.9.series
ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.1.series
ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.2.series
ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.3.series
ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.4.series
ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.5.series
ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.series

index a76432b..811b1f4 100644 (file)
@@ -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)
index 45e266d..3bd7483 100644 (file)
@@ -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 ; \
index 6deddd7..7cf59c9 100644 (file)
@@ -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;
index d8f2b22..966120a 100644 (file)
@@ -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;
index 75814a1..d24ef32 100644 (file)
@@ -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 (file)
index 0000000..2460066
--- /dev/null
@@ -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 <linux/rbtree.h>
+ #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 <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;
++}
++
++#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 <trace/events/ext4.h>
+ /*
+@@ -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);
index 7256e8f..a6abcab 100644 (file)
@@ -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;
index 811a6ef..2d9d130 100644 (file)
@@ -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 (file)
index 0000000..6351670
--- /dev/null
@@ -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 <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_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 <linux/iversion.h>
+ #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 <trace/events/ext4.h>
+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 <trace/events/ext4.h>
+ /*
+@@ -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);
index 12f4873..e7f8693 100644 (file)
@@ -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 (file)
index 0000000..728ffa8
--- /dev/null
@@ -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 <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_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 <linux/iversion.h>
+ #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 <trace/events/ext4.h>
+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 <trace/events/ext4.h>
+ /*
+@@ -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);
index 19caf50..1e5079f 100644 (file)
@@ -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
index 73e01c9..ebf4be8 100644 (file)
@@ -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
index 3415561..28726f8 100644 (file)
@@ -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
index f6d1ddf..a07d728 100644 (file)
@@ -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
index dd0475e..e1fa7c4 100644 (file)
@@ -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
index e5911c2..f884a28 100644 (file)
@@ -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
index 8092cd9..fd3d026 100644 (file)
@@ -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
index 97a3200..6856d42 100644 (file)
@@ -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
index 0a5a156..a40f4e1 100644 (file)
@@ -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
index 2bfffb9..56a2ca3 100644 (file)
@@ -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