]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
fscrypt: clean up and improve dentry revalidation
authorEric Biggers <ebiggers@google.com>
Wed, 20 Mar 2019 18:39:09 +0000 (11:39 -0700)
committerTheodore Ts'o <tytso@mit.edu>
Wed, 17 Apr 2019 13:48:46 +0000 (09:48 -0400)
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>
fs/crypto/crypto.c
fs/crypto/hooks.c
include/linux/dcache.h
include/linux/fscrypt.h

index 0988077882574ee2387d8935929038b9c3d01409..68e2ca4c4af63b6bc0330f3b1c21e4445a22beca 100644 (file)
@@ -307,45 +307,47 @@ int fscrypt_decrypt_page(const struct inode *inode, struct page *page,
 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 = fscrypt_has_encryption_key(d_inode(dir));
+       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 = {
index 56debb1fcf5eb49e48a6fa22b618c41158a5c1ca..a9492f75bbe13ffee7c14f69c4d8120ad049116a 100644 (file)
@@ -101,9 +101,9 @@ int __fscrypt_prepare_lookup(struct inode *dir, struct dentry *dentry)
        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);
        }
 
index 60996e64c579851accbcf0e70d2aa10f60181179..9b3b75d3bd211f4a5f86f056b1d2260d851e990a 100644 (file)
@@ -212,7 +212,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) */
index ec8ab71085994eea35dbd4642dacc13b509f9537..09e368a515d105efd761841eba8091ee74b4e11c 100644 (file)
@@ -545,10 +545,8 @@ static inline int fscrypt_prepare_rename(struct inode *old_dir,
  * 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