summaryrefslogtreecommitdiffstats
path: root/fs/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'fs/crypto')
-rw-r--r--fs/crypto/crypto.c58
-rw-r--r--fs/crypto/hooks.c4
2 files changed, 32 insertions, 30 deletions
diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
index 098807788257..68e2ca4c4af6 100644
--- a/fs/crypto/crypto.c
+++ b/fs/crypto/crypto.c
@@ -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 = {
diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c
index 56debb1fcf5e..a9492f75bbe1 100644
--- a/fs/crypto/hooks.c
+++ b/fs/crypto/hooks.c
@@ -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);
}