fscrypt caches decrypted symlink targets in inode->i_link. The VFS can return that target without calling the filesystem's ->get_link(), so access checks in that callback cannot revoke a previously cached link. CephFS subvolume quarantine needs those checks even when readlinkat() uses an empty path on an O_PATH descriptor opened before quarantine. Add a filesystem opt-out that prevents both creation-time and lookup- time cache installation. Leave cache readers unchanged: the option is fixed before inode creation, so they find no cached target. Uncached targets use the existing delayed-free callback. Opting out costs a temporary allocation and decryption on each access to an encrypted symlink, including readlink, traversal and lstat. Other filesystems keep their existing caching behavior. The Ceph opt-in is in the quarantine enforcement patch in this series. Signed-off-by: Alex Markuze --- fs/crypto/hooks.c | 28 +++++++++++++++------------- include/linux/fscrypt.h | 7 +++++++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c index a7a8a3f581a0..6bc7ac82ec34 100644 --- a/fs/crypto/hooks.c +++ b/fs/crypto/hooks.c @@ -321,11 +321,13 @@ int __fscrypt_encrypt_symlink(struct inode *inode, const char *target, */ sd->encrypted_path[ciphertext_len] = '\0'; - /* Cache the plaintext symlink target for later use by get_link() */ - err = -ENOMEM; - inode->i_link = kmemdup(target, len + 1, GFP_NOFS); - if (!inode->i_link) - goto err_free_sd; + /* Cache the plaintext target unless the filesystem revalidates it. */ + if (!inode->i_sb->s_cop->no_symlink_cache) { + err = -ENOMEM; + inode->i_link = kmemdup(target, len + 1, GFP_NOFS); + if (!inode->i_link) + goto err_free_sd; + } if (!disk_link->name) disk_link->name = (unsigned char *)sd; @@ -411,12 +413,12 @@ const char *fscrypt_get_symlink(struct inode *inode, const void *caddr, pstr.name[pstr.len] = '\0'; /* - * Cache decrypted symlink targets in i_link for later use. Don't cache - * symlink targets encoded without the key, since those become outdated - * once the key is added. This pairs with the READ_ONCE() above and in - * the VFS path lookup code. + * Cache decrypted targets unless the filesystem revalidates them. + * Don't cache targets encoded without the key, since those become + * outdated once the key is added. This pairs with the READ_ONCE() + * above and in the VFS path lookup code. */ - if (!has_key || + if (inode->i_sb->s_cop->no_symlink_cache || !has_key || cmpxchg_release(&inode->i_link, NULL, pstr.name) != NULL) set_delayed_call(done, kfree_link, pstr.name); @@ -442,9 +444,9 @@ EXPORT_SYMBOL_GPL(fscrypt_get_symlink); * This requires reading the symlink target from disk if needed, setting up the * inode's encryption key if possible, and then decrypting or encoding the * symlink target. This makes lstat() more heavyweight than is normally the - * case. However, decrypted symlink targets will be cached in ->i_link, so - * usually the symlink won't have to be read and decrypted again later if/when - * it is actually followed, readlink() is called, or lstat() is called again. + * case. Unless the filesystem sets no_symlink_cache, decrypted targets + * are cached in ->i_link, avoiding another read and decryption when the + * symlink is followed, readlink() is called, or lstat() is called again. * * Return: 0 on success, -errno on failure */ diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 28f2108e29f3..ab77e685c75c 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -107,6 +107,13 @@ struct fscrypt_operations { */ unsigned int supports_subblock_data_units : 1; + /* + * Don't populate ->i_link, so every access calls ->get_link() and the + * filesystem can revalidate access. Set before creating any inodes; + * this option cannot invalidate previously cached targets. + */ + unsigned int no_symlink_cache : 1; + /* * This field exists only for backwards compatibility reasons and should * only be set by the filesystems that are setting it already. It