]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
4.19-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 1 Nov 2020 10:39:02 +0000 (11:39 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 1 Nov 2020 10:39:02 +0000 (11:39 +0100)
added patches:
fs-fscrypt-clear-dcache_encrypted_name-when-unaliasing-directory.patch
fscrypt-clean-up-and-improve-dentry-revalidation.patch
fscrypt-fix-race-allowing-rename-and-link-of-ciphertext-dentries.patch
fscrypt-fix-race-where-lookup-marks-plaintext-dentry-as-ciphertext.patch
fscrypt-only-set-dentry_operations-on-ciphertext-dentries.patch
fscrypt-return-exdev-for-incompatible-rename-or-link-into-encrypted-dir.patch

queue-4.19/fs-fscrypt-clear-dcache_encrypted_name-when-unaliasing-directory.patch [new file with mode: 0644]
queue-4.19/fscrypt-clean-up-and-improve-dentry-revalidation.patch [new file with mode: 0644]
queue-4.19/fscrypt-fix-race-allowing-rename-and-link-of-ciphertext-dentries.patch [new file with mode: 0644]
queue-4.19/fscrypt-fix-race-where-lookup-marks-plaintext-dentry-as-ciphertext.patch [new file with mode: 0644]
queue-4.19/fscrypt-only-set-dentry_operations-on-ciphertext-dentries.patch [new file with mode: 0644]
queue-4.19/fscrypt-return-exdev-for-incompatible-rename-or-link-into-encrypted-dir.patch [new file with mode: 0644]
queue-4.19/series

diff --git a/queue-4.19/fs-fscrypt-clear-dcache_encrypted_name-when-unaliasing-directory.patch b/queue-4.19/fs-fscrypt-clear-dcache_encrypted_name-when-unaliasing-directory.patch
new file mode 100644 (file)
index 0000000..451a7bc
--- /dev/null
@@ -0,0 +1,64 @@
+From foo@baz Sun Nov  1 11:35:18 AM CET 2020
+From: Eric Biggers <ebiggers@kernel.org>
+Date: Sat, 31 Oct 2020 15:05:51 -0700
+Subject: fs, fscrypt: clear DCACHE_ENCRYPTED_NAME when unaliasing directory
+To: stable@vger.kernel.org
+Cc: linux-fscrypt@vger.kernel.org, linux-ext4@vger.kernel.org, linux-f2fs-devel@lists.sourceforge.net, linux-mtd@lists.infradead.org, Sarthak Kukreti <sarthakkukreti@chromium.org>, Theodore Ts'o <tytso@mit.edu>
+Message-ID: <20201031220553.1085782-4-ebiggers@kernel.org>
+
+From: Eric Biggers <ebiggers@google.com>
+
+commit 0bf3d5c1604ecbbd4e49e9f5b3c79152b87adb0d upstream.
+
+Make __d_move() clear DCACHE_ENCRYPTED_NAME on the source dentry.  This
+is needed for when d_splice_alias() moves a directory's encrypted alias
+to its decrypted alias as a result of the encryption key being added.
+
+Otherwise, the decrypted alias will incorrectly be invalidated on the
+next lookup, causing problems such as unmounting a mount the user just
+mount()ed there.
+
+Note that we don't have to support arbitrary moves of this flag because
+fscrypt doesn't allow dentries with DCACHE_ENCRYPTED_NAME to be the
+source or target of a rename().
+
+Fixes: 28b4c263961c ("ext4 crypto: revalidate dentry after adding or removing the key")
+Reported-by: Sarthak Kukreti <sarthakkukreti@chromium.org>
+Signed-off-by: Eric Biggers <ebiggers@google.com>
+Signed-off-by: Theodore Ts'o <tytso@mit.edu>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/dcache.c |   15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+--- a/fs/dcache.c
++++ b/fs/dcache.c
+@@ -2713,6 +2713,20 @@ static void copy_name(struct dentry *den
+ }
+ /*
++ * When d_splice_alias() moves a directory's encrypted alias to its decrypted
++ * alias as a result of the encryption key being added, DCACHE_ENCRYPTED_NAME
++ * must be cleared.  Note that we don't have to support arbitrary moves of this
++ * flag because fscrypt doesn't allow encrypted aliases to be the source or
++ * target of a rename().
++ */
++static inline void fscrypt_handle_d_move(struct dentry *dentry)
++{
++#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
++      dentry->d_flags &= ~DCACHE_ENCRYPTED_NAME;
++#endif
++}
++
++/*
+  * __d_move - move a dentry
+  * @dentry: entry to move
+  * @target: new dentry
+@@ -2787,6 +2801,7 @@ static void __d_move(struct dentry *dent
+       list_move(&dentry->d_child, &dentry->d_parent->d_subdirs);
+       __d_rehash(dentry);
+       fsnotify_update_flags(dentry);
++      fscrypt_handle_d_move(dentry);
+       write_seqcount_end(&target->d_seq);
+       write_seqcount_end(&dentry->d_seq);
diff --git a/queue-4.19/fscrypt-clean-up-and-improve-dentry-revalidation.patch b/queue-4.19/fscrypt-clean-up-and-improve-dentry-revalidation.patch
new file mode 100644 (file)
index 0000000..4860900
--- /dev/null
@@ -0,0 +1,156 @@
+From foo@baz Sun Nov  1 11:35:18 AM CET 2020
+From: Eric Biggers <ebiggers@kernel.org>
+Date: Sat, 31 Oct 2020 15:05:49 -0700
+Subject: fscrypt: clean up and improve dentry revalidation
+To: stable@vger.kernel.org
+Cc: linux-fscrypt@vger.kernel.org, linux-ext4@vger.kernel.org, linux-f2fs-devel@lists.sourceforge.net, linux-mtd@lists.infradead.org, Theodore Ts'o <tytso@mit.edu>
+Message-ID: <20201031220553.1085782-2-ebiggers@kernel.org>
+
+From: Eric Biggers <ebiggers@google.com>
+
+commit 6cc248684d3d23bbd073ae2fa73d3416c0558909 upstream.
+
+Make various improvements to fscrypt dentry revalidation:
+
+- Don't try to handle the case where the per-directory key is removed,
+  as this can't happen without the inode (and dentries) being evicted.
+
+- Flag ciphertext dentries rather than plaintext dentries, since it's
+  ciphertext dentries that need the special handling.
+
+- Avoid doing unnecessary work for non-ciphertext dentries.
+
+- When revalidating ciphertext dentries, try to set up the directory's
+  i_crypt_info to make sure the key is really still absent, rather than
+  invalidating all negative dentries as the previous code did.  An old
+  comment suggested we can't do this for locking reasons, but AFAICT
+  this comment was outdated and it actually works fine.
+
+Signed-off-by: Eric Biggers <ebiggers@google.com>
+Signed-off-by: Theodore Ts'o <tytso@mit.edu>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/crypto/crypto.c      |   58 ++++++++++++++++++++++++------------------------
+ fs/crypto/hooks.c       |    4 +--
+ include/linux/dcache.h  |    2 -
+ include/linux/fscrypt.h |    6 +---
+ 4 files changed, 35 insertions(+), 35 deletions(-)
+
+--- a/fs/crypto/crypto.c
++++ b/fs/crypto/crypto.c
+@@ -314,45 +314,47 @@ int fscrypt_decrypt_page(const struct in
+ EXPORT_SYMBOL(fscrypt_decrypt_page);
+ /*
+- * Validate dentries for encrypted directories to make sure we aren't
+- * potentially caching stale data after a key has been added or
+- * removed.
++ * Validate dentries in encrypted directories to make sure we aren't potentially
++ * caching stale dentries after a key has been added.
+  */
+ static int fscrypt_d_revalidate(struct dentry *dentry, unsigned int flags)
+ {
+       struct dentry *dir;
+-      int dir_has_key, cached_with_key;
++      int err;
++      int valid;
++
++      /*
++       * Plaintext names are always valid, since fscrypt doesn't support
++       * reverting to ciphertext names without evicting the directory's inode
++       * -- which implies eviction of the dentries in the directory.
++       */
++      if (!(dentry->d_flags & DCACHE_ENCRYPTED_NAME))
++              return 1;
++
++      /*
++       * Ciphertext name; valid if the directory's key is still unavailable.
++       *
++       * Although fscrypt forbids rename() on ciphertext names, we still must
++       * use dget_parent() here rather than use ->d_parent directly.  That's
++       * because a corrupted fs image may contain directory hard links, which
++       * the VFS handles by moving the directory's dentry tree in the dcache
++       * each time ->lookup() finds the directory and it already has a dentry
++       * elsewhere.  Thus ->d_parent can be changing, and we must safely grab
++       * a reference to some ->d_parent to prevent it from being freed.
++       */
+       if (flags & LOOKUP_RCU)
+               return -ECHILD;
+       dir = dget_parent(dentry);
+-      if (!IS_ENCRYPTED(d_inode(dir))) {
+-              dput(dir);
+-              return 0;
+-      }
+-
+-      spin_lock(&dentry->d_lock);
+-      cached_with_key = dentry->d_flags & DCACHE_ENCRYPTED_WITH_KEY;
+-      spin_unlock(&dentry->d_lock);
+-      dir_has_key = (d_inode(dir)->i_crypt_info != NULL);
++      err = fscrypt_get_encryption_info(d_inode(dir));
++      valid = !fscrypt_has_encryption_key(d_inode(dir));
+       dput(dir);
+-      /*
+-       * If the dentry was cached without the key, and it is a
+-       * negative dentry, it might be a valid name.  We can't check
+-       * if the key has since been made available due to locking
+-       * reasons, so we fail the validation so ext4_lookup() can do
+-       * this check.
+-       *
+-       * We also fail the validation if the dentry was created with
+-       * the key present, but we no longer have the key, or vice versa.
+-       */
+-      if ((!cached_with_key && d_is_negative(dentry)) ||
+-                      (!cached_with_key && dir_has_key) ||
+-                      (cached_with_key && !dir_has_key))
+-              return 0;
+-      return 1;
++      if (err < 0)
++              return err;
++
++      return valid;
+ }
+ const struct dentry_operations fscrypt_d_ops = {
+--- a/fs/crypto/hooks.c
++++ b/fs/crypto/hooks.c
+@@ -101,9 +101,9 @@ int __fscrypt_prepare_lookup(struct inod
+       if (err)
+               return err;
+-      if (fscrypt_has_encryption_key(dir)) {
++      if (!fscrypt_has_encryption_key(dir)) {
+               spin_lock(&dentry->d_lock);
+-              dentry->d_flags |= DCACHE_ENCRYPTED_WITH_KEY;
++              dentry->d_flags |= DCACHE_ENCRYPTED_NAME;
+               spin_unlock(&dentry->d_lock);
+       }
+--- a/include/linux/dcache.h
++++ b/include/linux/dcache.h
+@@ -210,7 +210,7 @@ struct dentry_operations {
+ #define DCACHE_MAY_FREE                       0x00800000
+ #define DCACHE_FALLTHRU                       0x01000000 /* Fall through to lower layer */
+-#define DCACHE_ENCRYPTED_WITH_KEY     0x02000000 /* dir is encrypted with a valid key */
++#define DCACHE_ENCRYPTED_NAME         0x02000000 /* Encrypted name (dir key was unavailable) */
+ #define DCACHE_OP_REAL                        0x04000000
+ #define DCACHE_PAR_LOOKUP             0x10000000 /* being looked up (with parent locked shared) */
+--- a/include/linux/fscrypt.h
++++ b/include/linux/fscrypt.h
+@@ -145,10 +145,8 @@ static inline int fscrypt_prepare_rename
+  * filenames are presented in encrypted form.  Therefore, we'll try to set up
+  * the directory's encryption key, but even without it the lookup can continue.
+  *
+- * To allow invalidating stale dentries if the directory's encryption key is
+- * added later, we also install a custom ->d_revalidate() method and use the
+- * DCACHE_ENCRYPTED_WITH_KEY flag to indicate whether a given dentry is a
+- * plaintext name (flag set) or a ciphertext name (flag cleared).
++ * This also installs a custom ->d_revalidate() method which will invalidate the
++ * dentry if it was created without the key and the key is later added.
+  *
+  * Return: 0 on success, -errno if a problem occurred while setting up the
+  * encryption key
diff --git a/queue-4.19/fscrypt-fix-race-allowing-rename-and-link-of-ciphertext-dentries.patch b/queue-4.19/fscrypt-fix-race-allowing-rename-and-link-of-ciphertext-dentries.patch
new file mode 100644 (file)
index 0000000..d039f11
--- /dev/null
@@ -0,0 +1,103 @@
+From foo@baz Sun Nov  1 11:35:18 AM CET 2020
+From: Eric Biggers <ebiggers@kernel.org>
+Date: Sat, 31 Oct 2020 15:05:50 -0700
+Subject: fscrypt: fix race allowing rename() and link() of ciphertext dentries
+To: stable@vger.kernel.org
+Cc: linux-fscrypt@vger.kernel.org, linux-ext4@vger.kernel.org, linux-f2fs-devel@lists.sourceforge.net, linux-mtd@lists.infradead.org, Theodore Ts'o <tytso@mit.edu>
+Message-ID: <20201031220553.1085782-3-ebiggers@kernel.org>
+
+From: Eric Biggers <ebiggers@google.com>
+
+commit 968dd6d0c6d6b6a989c6ddb9e2584a031b83e7b5 upstream.
+
+Close some race conditions where fscrypt allowed rename() and link() on
+ciphertext dentries that had been looked up just prior to the key being
+concurrently added.  It's better to return -ENOKEY in this case.
+
+This avoids doing the nonsensical thing of encrypting the names a second
+time when searching for the actual on-disk dir entries.  It also
+guarantees that DCACHE_ENCRYPTED_NAME dentries are never rename()d, so
+the dcache won't have support all possible combinations of moving
+DCACHE_ENCRYPTED_NAME around during __d_move().
+
+Signed-off-by: Eric Biggers <ebiggers@google.com>
+Signed-off-by: Theodore Ts'o <tytso@mit.edu>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/crypto/hooks.c               |   12 +++++++++++-
+ include/linux/fscrypt.h         |    2 +-
+ include/linux/fscrypt_notsupp.h |    4 ++--
+ include/linux/fscrypt_supp.h    |    3 ++-
+ 4 files changed, 16 insertions(+), 5 deletions(-)
+
+--- a/fs/crypto/hooks.c
++++ b/fs/crypto/hooks.c
+@@ -49,7 +49,8 @@ int fscrypt_file_open(struct inode *inod
+ }
+ EXPORT_SYMBOL_GPL(fscrypt_file_open);
+-int __fscrypt_prepare_link(struct inode *inode, struct inode *dir)
++int __fscrypt_prepare_link(struct inode *inode, struct inode *dir,
++                         struct dentry *dentry)
+ {
+       int err;
+@@ -57,6 +58,10 @@ int __fscrypt_prepare_link(struct inode
+       if (err)
+               return err;
++      /* ... in case we looked up ciphertext name before key was added */
++      if (dentry->d_flags & DCACHE_ENCRYPTED_NAME)
++              return -ENOKEY;
++
+       if (!fscrypt_has_permitted_context(dir, inode))
+               return -EXDEV;
+@@ -78,6 +83,11 @@ int __fscrypt_prepare_rename(struct inod
+       if (err)
+               return err;
++      /* ... in case we looked up ciphertext name(s) before key was added */
++      if ((old_dentry->d_flags | new_dentry->d_flags) &
++          DCACHE_ENCRYPTED_NAME)
++              return -ENOKEY;
++
+       if (old_dir != new_dir) {
+               if (IS_ENCRYPTED(new_dir) &&
+                   !fscrypt_has_permitted_context(new_dir,
+--- a/include/linux/fscrypt.h
++++ b/include/linux/fscrypt.h
+@@ -97,7 +97,7 @@ static inline int fscrypt_prepare_link(s
+                                      struct dentry *dentry)
+ {
+       if (IS_ENCRYPTED(dir))
+-              return __fscrypt_prepare_link(d_inode(old_dentry), dir);
++              return __fscrypt_prepare_link(d_inode(old_dentry), dir, dentry);
+       return 0;
+ }
+--- a/include/linux/fscrypt_notsupp.h
++++ b/include/linux/fscrypt_notsupp.h
+@@ -183,8 +183,8 @@ static inline int fscrypt_file_open(stru
+       return 0;
+ }
+-static inline int __fscrypt_prepare_link(struct inode *inode,
+-                                       struct inode *dir)
++static inline int __fscrypt_prepare_link(struct inode *inode, struct inode *dir,
++                                       struct dentry *dentry)
+ {
+       return -EOPNOTSUPP;
+ }
+--- a/include/linux/fscrypt_supp.h
++++ b/include/linux/fscrypt_supp.h
+@@ -184,7 +184,8 @@ extern int fscrypt_zeroout_range(const s
+ /* hooks.c */
+ extern int fscrypt_file_open(struct inode *inode, struct file *filp);
+-extern int __fscrypt_prepare_link(struct inode *inode, struct inode *dir);
++extern int __fscrypt_prepare_link(struct inode *inode, struct inode *dir,
++                                struct dentry *dentry);
+ extern int __fscrypt_prepare_rename(struct inode *old_dir,
+                                   struct dentry *old_dentry,
+                                   struct inode *new_dir,
diff --git a/queue-4.19/fscrypt-fix-race-where-lookup-marks-plaintext-dentry-as-ciphertext.patch b/queue-4.19/fscrypt-fix-race-where-lookup-marks-plaintext-dentry-as-ciphertext.patch
new file mode 100644 (file)
index 0000000..25e4009
--- /dev/null
@@ -0,0 +1,475 @@
+From foo@baz Sun Nov  1 11:35:18 AM CET 2020
+From: Eric Biggers <ebiggers@kernel.org>
+Date: Sat, 31 Oct 2020 15:05:53 -0700
+Subject: fscrypt: fix race where ->lookup() marks plaintext dentry as ciphertext
+To: stable@vger.kernel.org
+Cc: linux-fscrypt@vger.kernel.org, linux-ext4@vger.kernel.org, linux-f2fs-devel@lists.sourceforge.net, linux-mtd@lists.infradead.org, Theodore Ts'o <tytso@mit.edu>
+Message-ID: <20201031220553.1085782-6-ebiggers@kernel.org>
+
+From: Eric Biggers <ebiggers@google.com>
+
+commit b01531db6cec2aa330dbc91bfbfaaef4a0d387a4 upstream.
+
+->lookup() in an encrypted directory begins as follows:
+
+1. fscrypt_prepare_lookup():
+    a. Try to load the directory's encryption key.
+    b. If the key is unavailable, mark the dentry as a ciphertext name
+       via d_flags.
+2. fscrypt_setup_filename():
+    a. Try to load the directory's encryption key.
+    b. If the key is available, encrypt the name (treated as a plaintext
+       name) to get the on-disk name.  Otherwise decode the name
+       (treated as a ciphertext name) to get the on-disk name.
+
+But if the key is concurrently added, it may be found at (2a) but not at
+(1a).  In this case, the dentry will be wrongly marked as a ciphertext
+name even though it was actually treated as plaintext.
+
+This will cause the dentry to be wrongly invalidated on the next lookup,
+potentially causing problems.  For example, if the racy ->lookup() was
+part of sys_mount(), then the new mount will be detached when anything
+tries to access it.  This is despite the mountpoint having a plaintext
+path, which should remain valid now that the key was added.
+
+Of course, this is only possible if there's a userspace race.  Still,
+the additional kernel-side race is confusing and unexpected.
+
+Close the kernel-side race by changing fscrypt_prepare_lookup() to also
+set the on-disk filename (step 2b), consistent with the d_flags update.
+
+Fixes: 28b4c263961c ("ext4 crypto: revalidate dentry after adding or removing the key")
+Signed-off-by: Eric Biggers <ebiggers@google.com>
+Signed-off-by: Theodore Ts'o <tytso@mit.edu>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/crypto/fname.c               |    1 
+ fs/crypto/hooks.c               |   11 +++--
+ fs/ext4/ext4.h                  |   62 ++++++++++++++++++++++++--------
+ fs/ext4/namei.c                 |   76 +++++++++++++++++++++++++++-------------
+ fs/f2fs/namei.c                 |   17 +++++---
+ fs/ubifs/dir.c                  |    8 +---
+ include/linux/fscrypt.h         |   22 +++++++----
+ include/linux/fscrypt_notsupp.h |    5 +-
+ include/linux/fscrypt_supp.h    |    3 +
+ 9 files changed, 139 insertions(+), 66 deletions(-)
+
+--- a/fs/crypto/fname.c
++++ b/fs/crypto/fname.c
+@@ -354,6 +354,7 @@ int fscrypt_setup_filename(struct inode
+       }
+       if (!lookup)
+               return -ENOKEY;
++      fname->is_ciphertext_name = true;
+       /*
+        * We don't have the key and we are doing a lookup; decode the
+--- a/fs/crypto/hooks.c
++++ b/fs/crypto/hooks.c
+@@ -104,20 +104,21 @@ int __fscrypt_prepare_rename(struct inod
+ }
+ EXPORT_SYMBOL_GPL(__fscrypt_prepare_rename);
+-int __fscrypt_prepare_lookup(struct inode *dir, struct dentry *dentry)
++int __fscrypt_prepare_lookup(struct inode *dir, struct dentry *dentry,
++                           struct fscrypt_name *fname)
+ {
+-      int err = fscrypt_get_encryption_info(dir);
++      int err = fscrypt_setup_filename(dir, &dentry->d_name, 1, fname);
+-      if (err)
++      if (err && err != -ENOENT)
+               return err;
+-      if (!fscrypt_has_encryption_key(dir)) {
++      if (fname->is_ciphertext_name) {
+               spin_lock(&dentry->d_lock);
+               dentry->d_flags |= DCACHE_ENCRYPTED_NAME;
+               spin_unlock(&dentry->d_lock);
+               d_set_d_op(dentry, &fscrypt_d_ops);
+       }
+-      return 0;
++      return err;
+ }
+ EXPORT_SYMBOL_GPL(__fscrypt_prepare_lookup);
+--- a/fs/ext4/ext4.h
++++ b/fs/ext4/ext4.h
+@@ -2326,23 +2326,47 @@ static inline bool ext4_encrypted_inode(
+ }
+ #ifdef CONFIG_EXT4_FS_ENCRYPTION
++static inline void ext4_fname_from_fscrypt_name(struct ext4_filename *dst,
++                                              const struct fscrypt_name *src)
++{
++      memset(dst, 0, sizeof(*dst));
++
++      dst->usr_fname = src->usr_fname;
++      dst->disk_name = src->disk_name;
++      dst->hinfo.hash = src->hash;
++      dst->hinfo.minor_hash = src->minor_hash;
++      dst->crypto_buf = src->crypto_buf;
++}
++
+ static inline int ext4_fname_setup_filename(struct inode *dir,
+-                      const struct qstr *iname,
+-                      int lookup, struct ext4_filename *fname)
++                                          const struct qstr *iname,
++                                          int lookup,
++                                          struct ext4_filename *fname)
+ {
+       struct fscrypt_name name;
+       int err;
+-      memset(fname, 0, sizeof(struct ext4_filename));
+-
+       err = fscrypt_setup_filename(dir, iname, lookup, &name);
++      if (err)
++              return err;
+-      fname->usr_fname = name.usr_fname;
+-      fname->disk_name = name.disk_name;
+-      fname->hinfo.hash = name.hash;
+-      fname->hinfo.minor_hash = name.minor_hash;
+-      fname->crypto_buf = name.crypto_buf;
+-      return err;
++      ext4_fname_from_fscrypt_name(fname, &name);
++      return 0;
++}
++
++static inline int ext4_fname_prepare_lookup(struct inode *dir,
++                                          struct dentry *dentry,
++                                          struct ext4_filename *fname)
++{
++      struct fscrypt_name name;
++      int err;
++
++      err = fscrypt_prepare_lookup(dir, dentry, &name);
++      if (err)
++              return err;
++
++      ext4_fname_from_fscrypt_name(fname, &name);
++      return 0;
+ }
+ static inline void ext4_fname_free_filename(struct ext4_filename *fname)
+@@ -2356,19 +2380,27 @@ static inline void ext4_fname_free_filen
+       fname->usr_fname = NULL;
+       fname->disk_name.name = NULL;
+ }
+-#else
++#else /* !CONFIG_EXT4_FS_ENCRYPTION */
+ static inline int ext4_fname_setup_filename(struct inode *dir,
+-              const struct qstr *iname,
+-              int lookup, struct ext4_filename *fname)
++                                          const struct qstr *iname,
++                                          int lookup,
++                                          struct ext4_filename *fname)
+ {
+       fname->usr_fname = iname;
+       fname->disk_name.name = (unsigned char *) iname->name;
+       fname->disk_name.len = iname->len;
+       return 0;
+ }
+-static inline void ext4_fname_free_filename(struct ext4_filename *fname) { }
+-#endif
++static inline int ext4_fname_prepare_lookup(struct inode *dir,
++                                          struct dentry *dentry,
++                                          struct ext4_filename *fname)
++{
++      return ext4_fname_setup_filename(dir, &dentry->d_name, 1, fname);
++}
++
++static inline void ext4_fname_free_filename(struct ext4_filename *fname) { }
++#endif /* !CONFIG_EXT4_FS_ENCRYPTION */
+ /* dir.c */
+ extern int __ext4_check_dir_entry(const char *, unsigned int, struct inode *,
+--- a/fs/ext4/namei.c
++++ b/fs/ext4/namei.c
+@@ -1343,7 +1343,7 @@ static int is_dx_internal_node(struct in
+ }
+ /*
+- *    ext4_find_entry()
++ *    __ext4_find_entry()
+  *
+  * finds an entry in the specified directory with the wanted name. It
+  * returns the cache buffer in which the entry was found, and the entry
+@@ -1353,39 +1353,32 @@ static int is_dx_internal_node(struct in
+  * The returned buffer_head has ->b_count elevated.  The caller is expected
+  * to brelse() it when appropriate.
+  */
+-static struct buffer_head * ext4_find_entry (struct inode *dir,
+-                                      const struct qstr *d_name,
+-                                      struct ext4_dir_entry_2 **res_dir,
+-                                      int *inlined)
++static struct buffer_head *__ext4_find_entry(struct inode *dir,
++                                           struct ext4_filename *fname,
++                                           struct ext4_dir_entry_2 **res_dir,
++                                           int *inlined)
+ {
+       struct super_block *sb;
+       struct buffer_head *bh_use[NAMEI_RA_SIZE];
+       struct buffer_head *bh, *ret = NULL;
+       ext4_lblk_t start, block;
+-      const u8 *name = d_name->name;
++      const u8 *name = fname->usr_fname->name;
+       size_t ra_max = 0;      /* Number of bh's in the readahead
+                                  buffer, bh_use[] */
+       size_t ra_ptr = 0;      /* Current index into readahead
+                                  buffer */
+       ext4_lblk_t  nblocks;
+       int i, namelen, retval;
+-      struct ext4_filename fname;
+       *res_dir = NULL;
+       sb = dir->i_sb;
+-      namelen = d_name->len;
++      namelen = fname->usr_fname->len;
+       if (namelen > EXT4_NAME_LEN)
+               return NULL;
+-      retval = ext4_fname_setup_filename(dir, d_name, 1, &fname);
+-      if (retval == -ENOENT)
+-              return NULL;
+-      if (retval)
+-              return ERR_PTR(retval);
+-
+       if (ext4_has_inline_data(dir)) {
+               int has_inline_data = 1;
+-              ret = ext4_find_inline_entry(dir, &fname, res_dir,
++              ret = ext4_find_inline_entry(dir, fname, res_dir,
+                                            &has_inline_data);
+               if (has_inline_data) {
+                       if (inlined)
+@@ -1405,7 +1398,7 @@ static struct buffer_head * ext4_find_en
+               goto restart;
+       }
+       if (is_dx(dir)) {
+-              ret = ext4_dx_find_entry(dir, &fname, res_dir);
++              ret = ext4_dx_find_entry(dir, fname, res_dir);
+               /*
+                * On success, or if the error was file not found,
+                * return.  Otherwise, fall back to doing a search the
+@@ -1470,7 +1463,7 @@ restart:
+                       goto cleanup_and_exit;
+               }
+               set_buffer_verified(bh);
+-              i = search_dirblock(bh, dir, &fname,
++              i = search_dirblock(bh, dir, fname,
+                           block << EXT4_BLOCK_SIZE_BITS(sb), res_dir);
+               if (i == 1) {
+                       EXT4_I(dir)->i_dir_start_lookup = block;
+@@ -1501,10 +1494,50 @@ 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);
+       return ret;
+ }
++static struct buffer_head *ext4_find_entry(struct inode *dir,
++                                         const struct qstr *d_name,
++                                         struct ext4_dir_entry_2 **res_dir,
++                                         int *inlined)
++{
++      int err;
++      struct ext4_filename fname;
++      struct buffer_head *bh;
++
++      err = ext4_fname_setup_filename(dir, d_name, 1, &fname);
++      if (err == -ENOENT)
++              return NULL;
++      if (err)
++              return ERR_PTR(err);
++
++      bh = __ext4_find_entry(dir, &fname, res_dir, inlined);
++
++      ext4_fname_free_filename(&fname);
++      return bh;
++}
++
++static struct buffer_head *ext4_lookup_entry(struct inode *dir,
++                                           struct dentry *dentry,
++                                           struct ext4_dir_entry_2 **res_dir)
++{
++      int err;
++      struct ext4_filename fname;
++      struct buffer_head *bh;
++
++      err = ext4_fname_prepare_lookup(dir, dentry, &fname);
++      if (err == -ENOENT)
++              return NULL;
++      if (err)
++              return ERR_PTR(err);
++
++      bh = __ext4_find_entry(dir, &fname, res_dir, NULL);
++
++      ext4_fname_free_filename(&fname);
++      return bh;
++}
++
+ static struct buffer_head * ext4_dx_find_entry(struct inode *dir,
+                       struct ext4_filename *fname,
+                       struct ext4_dir_entry_2 **res_dir)
+@@ -1563,16 +1596,11 @@ static struct dentry *ext4_lookup(struct
+       struct inode *inode;
+       struct ext4_dir_entry_2 *de;
+       struct buffer_head *bh;
+-      int err;
+-
+-      err = fscrypt_prepare_lookup(dir, dentry, flags);
+-      if (err)
+-              return ERR_PTR(err);
+       if (dentry->d_name.len > EXT4_NAME_LEN)
+               return ERR_PTR(-ENAMETOOLONG);
+-      bh = ext4_find_entry(dir, &dentry->d_name, &de, NULL);
++      bh = ext4_lookup_entry(dir, dentry, &de);
+       if (IS_ERR(bh))
+               return (struct dentry *) bh;
+       inode = NULL;
+--- a/fs/f2fs/namei.c
++++ b/fs/f2fs/namei.c
+@@ -432,19 +432,23 @@ static struct dentry *f2fs_lookup(struct
+       nid_t ino = -1;
+       int err = 0;
+       unsigned int root_ino = F2FS_ROOT_INO(F2FS_I_SB(dir));
++      struct fscrypt_name fname;
+       trace_f2fs_lookup_start(dir, dentry, flags);
+-      err = fscrypt_prepare_lookup(dir, dentry, flags);
+-      if (err)
+-              goto out;
+-
+       if (dentry->d_name.len > F2FS_NAME_LEN) {
+               err = -ENAMETOOLONG;
+               goto out;
+       }
+-      de = f2fs_find_entry(dir, &dentry->d_name, &page);
++      err = fscrypt_prepare_lookup(dir, dentry, &fname);
++      if (err == -ENOENT)
++              goto out_splice;
++      if (err)
++              goto out;
++      de = __f2fs_find_entry(dir, &fname, &page);
++      fscrypt_free_filename(&fname);
++
+       if (!de) {
+               if (IS_ERR(page)) {
+                       err = PTR_ERR(page);
+@@ -484,8 +488,7 @@ static struct dentry *f2fs_lookup(struct
+       }
+ out_splice:
+       new = d_splice_alias(inode, dentry);
+-      if (IS_ERR(new))
+-              err = PTR_ERR(new);
++      err = PTR_ERR_OR_ZERO(new);
+       trace_f2fs_lookup_end(dir, dentry, ino, err);
+       return new;
+ out_iput:
+--- a/fs/ubifs/dir.c
++++ b/fs/ubifs/dir.c
+@@ -220,11 +220,9 @@ static struct dentry *ubifs_lookup(struc
+       dbg_gen("'%pd' in dir ino %lu", dentry, dir->i_ino);
+-      err = fscrypt_prepare_lookup(dir, dentry, flags);
+-      if (err)
+-              return ERR_PTR(err);
+-
+-      err = fscrypt_setup_filename(dir, &dentry->d_name, 1, &nm);
++      err = fscrypt_prepare_lookup(dir, dentry, &nm);
++      if (err == -ENOENT)
++              return d_splice_alias(NULL, dentry);
+       if (err)
+               return ERR_PTR(err);
+--- a/include/linux/fscrypt.h
++++ b/include/linux/fscrypt.h
+@@ -32,6 +32,7 @@ struct fscrypt_name {
+       u32 hash;
+       u32 minor_hash;
+       struct fscrypt_str crypto_buf;
++      bool is_ciphertext_name;
+ };
+ #define FSTR_INIT(n, l)               { .name = n, .len = l }
+@@ -138,25 +139,32 @@ static inline int fscrypt_prepare_rename
+  * fscrypt_prepare_lookup - prepare to lookup a name in a possibly-encrypted directory
+  * @dir: directory being searched
+  * @dentry: filename being looked up
+- * @flags: lookup flags
++ * @fname: (output) the name to use to search the on-disk directory
+  *
+- * Prepare for ->lookup() in a directory which may be encrypted.  Lookups can be
+- * done with or without the directory's encryption key; without the key,
++ * Prepare for ->lookup() in a directory which may be encrypted by determining
++ * the name that will actually be used to search the directory on-disk.  Lookups
++ * can be done with or without the directory's encryption key; without the key,
+  * filenames are presented in encrypted form.  Therefore, we'll try to set up
+  * the directory's encryption key, but even without it the lookup can continue.
+  *
+  * This also installs a custom ->d_revalidate() method which will invalidate the
+  * dentry if it was created without the key and the key is later added.
+  *
+- * Return: 0 on success, -errno if a problem occurred while setting up the
+- * encryption key
++ * Return: 0 on success; -ENOENT if key is unavailable but the filename isn't a
++ * correctly formed encoded ciphertext name, so a negative dentry should be
++ * created; or another -errno code.
+  */
+ static inline int fscrypt_prepare_lookup(struct inode *dir,
+                                        struct dentry *dentry,
+-                                       unsigned int flags)
++                                       struct fscrypt_name *fname)
+ {
+       if (IS_ENCRYPTED(dir))
+-              return __fscrypt_prepare_lookup(dir, dentry);
++              return __fscrypt_prepare_lookup(dir, dentry, fname);
++
++      memset(fname, 0, sizeof(*fname));
++      fname->usr_fname = &dentry->d_name;
++      fname->disk_name.name = (unsigned char *)dentry->d_name.name;
++      fname->disk_name.len = dentry->d_name.len;
+       return 0;
+ }
+--- a/include/linux/fscrypt_notsupp.h
++++ b/include/linux/fscrypt_notsupp.h
+@@ -112,7 +112,7 @@ static inline int fscrypt_setup_filename
+       if (IS_ENCRYPTED(dir))
+               return -EOPNOTSUPP;
+-      memset(fname, 0, sizeof(struct fscrypt_name));
++      memset(fname, 0, sizeof(*fname));
+       fname->usr_fname = iname;
+       fname->disk_name.name = (unsigned char *)iname->name;
+       fname->disk_name.len = iname->len;
+@@ -199,7 +199,8 @@ static inline int __fscrypt_prepare_rena
+ }
+ static inline int __fscrypt_prepare_lookup(struct inode *dir,
+-                                         struct dentry *dentry)
++                                         struct dentry *dentry,
++                                         struct fscrypt_name *fname)
+ {
+       return -EOPNOTSUPP;
+ }
+--- a/include/linux/fscrypt_supp.h
++++ b/include/linux/fscrypt_supp.h
+@@ -191,7 +191,8 @@ extern int __fscrypt_prepare_rename(stru
+                                   struct inode *new_dir,
+                                   struct dentry *new_dentry,
+                                   unsigned int flags);
+-extern int __fscrypt_prepare_lookup(struct inode *dir, struct dentry *dentry);
++extern int __fscrypt_prepare_lookup(struct inode *dir, struct dentry *dentry,
++                                  struct fscrypt_name *fname);
+ extern int __fscrypt_prepare_symlink(struct inode *dir, unsigned int len,
+                                    unsigned int max_len,
+                                    struct fscrypt_str *disk_link);
diff --git a/queue-4.19/fscrypt-only-set-dentry_operations-on-ciphertext-dentries.patch b/queue-4.19/fscrypt-only-set-dentry_operations-on-ciphertext-dentries.patch
new file mode 100644 (file)
index 0000000..4e3eedd
--- /dev/null
@@ -0,0 +1,51 @@
+From foo@baz Sun Nov  1 11:35:18 AM CET 2020
+From: Eric Biggers <ebiggers@kernel.org>
+Date: Sat, 31 Oct 2020 15:05:52 -0700
+Subject: fscrypt: only set dentry_operations on ciphertext dentries
+To: stable@vger.kernel.org
+Cc: linux-fscrypt@vger.kernel.org, linux-ext4@vger.kernel.org, linux-f2fs-devel@lists.sourceforge.net, linux-mtd@lists.infradead.org, Theodore Ts'o <tytso@mit.edu>
+Message-ID: <20201031220553.1085782-5-ebiggers@kernel.org>
+
+From: Eric Biggers <ebiggers@google.com>
+
+commit d456a33f041af4b54f3ce495a86d00c246165032 upstream.
+
+Plaintext dentries are always valid, so only set fscrypt_d_ops on
+ciphertext dentries.
+
+Besides marginally improved performance, this allows overlayfs to use an
+fscrypt-encrypted upperdir, provided that all the following are true:
+
+    (1) The fscrypt encryption key is placed in the keyring before
+       mounting overlayfs, and remains while the overlayfs is mounted.
+
+    (2) The overlayfs workdir uses the same encryption policy.
+
+    (3) No dentries for the ciphertext names of subdirectories have been
+       created in the upperdir or workdir yet.  (Since otherwise
+       d_splice_alias() will reuse the old dentry with ->d_op set.)
+
+One potential use case is using an ephemeral encryption key to encrypt
+all files created or changed by a container, so that they can be
+securely erased ("crypto-shredded") after the container stops.
+
+Signed-off-by: Eric Biggers <ebiggers@google.com>
+Signed-off-by: Theodore Ts'o <tytso@mit.edu>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/crypto/hooks.c |    3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/fs/crypto/hooks.c
++++ b/fs/crypto/hooks.c
+@@ -115,9 +115,8 @@ int __fscrypt_prepare_lookup(struct inod
+               spin_lock(&dentry->d_lock);
+               dentry->d_flags |= DCACHE_ENCRYPTED_NAME;
+               spin_unlock(&dentry->d_lock);
++              d_set_d_op(dentry, &fscrypt_d_ops);
+       }
+-
+-      d_set_d_op(dentry, &fscrypt_d_ops);
+       return 0;
+ }
+ EXPORT_SYMBOL_GPL(__fscrypt_prepare_lookup);
diff --git a/queue-4.19/fscrypt-return-exdev-for-incompatible-rename-or-link-into-encrypted-dir.patch b/queue-4.19/fscrypt-return-exdev-for-incompatible-rename-or-link-into-encrypted-dir.patch
new file mode 100644 (file)
index 0000000..e79124d
--- /dev/null
@@ -0,0 +1,158 @@
+From f5e55e777cc93eae1416f0fa4908e8846b6d7825 Mon Sep 17 00:00:00 2001
+From: Eric Biggers <ebiggers@google.com>
+Date: Tue, 22 Jan 2019 16:20:21 -0800
+Subject: fscrypt: return -EXDEV for incompatible rename or link into encrypted dir
+
+From: Eric Biggers <ebiggers@google.com>
+
+commit f5e55e777cc93eae1416f0fa4908e8846b6d7825 upstream.
+
+Currently, trying to rename or link a regular file, directory, or
+symlink into an encrypted directory fails with EPERM when the source
+file is unencrypted or is encrypted with a different encryption policy,
+and is on the same mountpoint.  It is correct for the operation to fail,
+but the choice of EPERM breaks tools like 'mv' that know to copy rather
+than rename if they see EXDEV, but don't know what to do with EPERM.
+
+Our original motivation for EPERM was to encourage users to securely
+handle their data.  Encrypting files by "moving" them into an encrypted
+directory can be insecure because the unencrypted data may remain in
+free space on disk, where it can later be recovered by an attacker.
+It's much better to encrypt the data from the start, or at least try to
+securely delete the source data e.g. using the 'shred' program.
+
+However, the current behavior hasn't been effective at achieving its
+goal because users tend to be confused, hack around it, and complain;
+see e.g. https://github.com/google/fscrypt/issues/76.  And in some cases
+it's actually inconsistent or unnecessary.  For example, 'mv'-ing files
+between differently encrypted directories doesn't work even in cases
+where it can be secure, such as when in userspace the same passphrase
+protects both directories.  Yet, you *can* already 'mv' unencrypted
+files into an encrypted directory if the source files are on a different
+mountpoint, even though doing so is often insecure.
+
+There are probably better ways to teach users to securely handle their
+files.  For example, the 'fscrypt' userspace tool could provide a
+command that migrates unencrypted files into an encrypted directory,
+acting like 'shred' on the source files and providing appropriate
+warnings depending on the type of the source filesystem and disk.
+
+Receiving errors on unimportant files might also force some users to
+disable encryption, thus making the behavior counterproductive.  It's
+desirable to make encryption as unobtrusive as possible.
+
+Therefore, change the error code from EPERM to EXDEV so that tools
+looking for EXDEV will fall back to a copy.
+
+This, of course, doesn't prevent users from still doing the right things
+to securely manage their files.  Note that this also matches the
+behavior when a file is renamed between two project quota hierarchies;
+so there's precedent for using EXDEV for things other than mountpoints.
+
+xfstests generic/398 will require an update with this change.
+
+[Rewritten from an earlier patch series by Michael Halcrow.]
+
+Cc: Michael Halcrow <mhalcrow@google.com>
+Cc: Joe Richey <joerichey@google.com>
+Signed-off-by: Eric Biggers <ebiggers@google.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+
+---
+ Documentation/filesystems/fscrypt.rst |   12 ++++++++++--
+ fs/crypto/hooks.c                     |    6 +++---
+ fs/crypto/policy.c                    |    3 +--
+ include/linux/fscrypt.h               |    4 ++--
+ 4 files changed, 16 insertions(+), 9 deletions(-)
+
+--- a/Documentation/filesystems/fscrypt.rst
++++ b/Documentation/filesystems/fscrypt.rst
+@@ -426,10 +426,18 @@ astute users may notice some differences
+ - Unencrypted files, or files encrypted with a different encryption
+   policy (i.e. different key, modes, or flags), cannot be renamed or
+   linked into an encrypted directory; see `Encryption policy
+-  enforcement`_.  Attempts to do so will fail with EPERM.  However,
++  enforcement`_.  Attempts to do so will fail with EXDEV.  However,
+   encrypted files can be renamed within an encrypted directory, or
+   into an unencrypted directory.
++  Note: "moving" an unencrypted file into an encrypted directory, e.g.
++  with the `mv` program, is implemented in userspace by a copy
++  followed by a delete.  Be aware that the original unencrypted data
++  may remain recoverable from free space on the disk; prefer to keep
++  all files encrypted from the very beginning.  The `shred` program
++  may be used to overwrite the source files but isn't guaranteed to be
++  effective on all filesystems and storage devices.
++
+ - Direct I/O is not supported on encrypted files.  Attempts to use
+   direct I/O on such files will fall back to buffered I/O.
+@@ -516,7 +524,7 @@ not be encrypted.
+ Except for those special files, it is forbidden to have unencrypted
+ files, or files encrypted with a different encryption policy, in an
+ encrypted directory tree.  Attempts to link or rename such a file into
+-an encrypted directory will fail with EPERM.  This is also enforced
++an encrypted directory will fail with EXDEV.  This is also enforced
+ during ->lookup() to provide limited protection against offline
+ attacks that try to disable or downgrade encryption in known locations
+ where applications may later write sensitive data.  It is recommended
+--- a/fs/crypto/hooks.c
++++ b/fs/crypto/hooks.c
+@@ -58,7 +58,7 @@ int __fscrypt_prepare_link(struct inode
+               return err;
+       if (!fscrypt_has_permitted_context(dir, inode))
+-              return -EPERM;
++              return -EXDEV;
+       return 0;
+ }
+@@ -82,13 +82,13 @@ int __fscrypt_prepare_rename(struct inod
+               if (IS_ENCRYPTED(new_dir) &&
+                   !fscrypt_has_permitted_context(new_dir,
+                                                  d_inode(old_dentry)))
+-                      return -EPERM;
++                      return -EXDEV;
+               if ((flags & RENAME_EXCHANGE) &&
+                   IS_ENCRYPTED(old_dir) &&
+                   !fscrypt_has_permitted_context(old_dir,
+                                                  d_inode(new_dentry)))
+-                      return -EPERM;
++                      return -EXDEV;
+       }
+       return 0;
+ }
+--- a/fs/crypto/policy.c
++++ b/fs/crypto/policy.c
+@@ -153,8 +153,7 @@ EXPORT_SYMBOL(fscrypt_ioctl_get_policy);
+  * malicious offline violations of this constraint, while the link and rename
+  * checks are needed to prevent online violations of this constraint.
+  *
+- * Return: 1 if permitted, 0 if forbidden.  If forbidden, the caller must fail
+- * the filesystem operation with EPERM.
++ * Return: 1 if permitted, 0 if forbidden.
+  */
+ int fscrypt_has_permitted_context(struct inode *parent, struct inode *child)
+ {
+--- a/include/linux/fscrypt.h
++++ b/include/linux/fscrypt.h
+@@ -89,7 +89,7 @@ static inline int fscrypt_require_key(st
+  * in an encrypted directory tree use the same encryption policy.
+  *
+  * Return: 0 on success, -ENOKEY if the directory's encryption key is missing,
+- * -EPERM if the link would result in an inconsistent encryption policy, or
++ * -EXDEV if the link would result in an inconsistent encryption policy, or
+  * another -errno code.
+  */
+ static inline int fscrypt_prepare_link(struct dentry *old_dentry,
+@@ -119,7 +119,7 @@ static inline int fscrypt_prepare_link(s
+  * We also verify that the rename will not violate the constraint that all files
+  * in an encrypted directory tree use the same encryption policy.
+  *
+- * Return: 0 on success, -ENOKEY if an encryption key is missing, -EPERM if the
++ * Return: 0 on success, -ENOKEY if an encryption key is missing, -EXDEV if the
+  * rename would cause inconsistent encryption policies, or another -errno code.
+  */
+ static inline int fscrypt_prepare_rename(struct inode *old_dir,
index 6b9412368640b113d0813afd811407f13966d4c5..c1f424bf45af179adc3e8eba1f0e923296b5137c 100644 (file)
@@ -26,3 +26,9 @@ rdma-addr-fix-race-with-netevent_callback-rdma_addr_cancel.patch
 mtd-lpddr-fix-bad-logic-in-print_drs_error.patch
 serial-pl011-fix-lockdep-splat-when-handling-magic-sysrq-interrupt.patch
 ata-sata_rcar-fix-dma-boundary-mask.patch
+fscrypt-return-exdev-for-incompatible-rename-or-link-into-encrypted-dir.patch
+fscrypt-clean-up-and-improve-dentry-revalidation.patch
+fscrypt-fix-race-allowing-rename-and-link-of-ciphertext-dentries.patch
+fs-fscrypt-clear-dcache_encrypted_name-when-unaliasing-directory.patch
+fscrypt-only-set-dentry_operations-on-ciphertext-dentries.patch
+fscrypt-fix-race-where-lookup-marks-plaintext-dentry-as-ciphertext.patch