fscrypt: cache decrypted symlink target in ->i_link
Path lookups that traverse encrypted symlink(s) are very slow because each encrypted symlink needs to be decrypted each time it's followed. This also involves dropping out of rcu-walk mode. Make encrypted symlinks faster by caching the decrypted symlink target in ->i_link. The first call to fscrypt_get_symlink() sets it. Then, the existing VFS path lookup code uses the non-NULL ->i_link to take the fast path where ->get_link() isn't called, and lookups in rcu-walk mode remain in rcu-walk mode. Also set ->i_link immediately when a new encrypted symlink is created. To safely free the symlink target after an RCU grace period has elapsed, introduce a new function fscrypt_free_inode(), and make the relevant filesystems call it just before actually freeing the inode. Cc: Al Viro <viro@zeniv.linux.org.uk> Signed-off-by: Eric Biggers <ebiggers@google.com> Signed-off-by: Theodore Ts'o <tytso@mit.edu>
This commit is contained in:
parent
4c4f7c19b3
commit
2c58d548f5
@ -189,11 +189,9 @@ int __fscrypt_encrypt_symlink(struct inode *inode, const char *target,
|
|||||||
sd->len = cpu_to_le16(ciphertext_len);
|
sd->len = cpu_to_le16(ciphertext_len);
|
||||||
|
|
||||||
err = fname_encrypt(inode, &iname, sd->encrypted_path, ciphertext_len);
|
err = fname_encrypt(inode, &iname, sd->encrypted_path, ciphertext_len);
|
||||||
if (err) {
|
if (err)
|
||||||
if (!disk_link->name)
|
goto err_free_sd;
|
||||||
kfree(sd);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
* Null-terminating the ciphertext doesn't make sense, but we still
|
* Null-terminating the ciphertext doesn't make sense, but we still
|
||||||
* count the null terminator in the length, so we might as well
|
* count the null terminator in the length, so we might as well
|
||||||
@ -201,9 +199,20 @@ int __fscrypt_encrypt_symlink(struct inode *inode, const char *target,
|
|||||||
*/
|
*/
|
||||||
sd->encrypted_path[ciphertext_len] = '\0';
|
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;
|
||||||
|
|
||||||
if (!disk_link->name)
|
if (!disk_link->name)
|
||||||
disk_link->name = (unsigned char *)sd;
|
disk_link->name = (unsigned char *)sd;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
err_free_sd:
|
||||||
|
if (!disk_link->name)
|
||||||
|
kfree(sd);
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(__fscrypt_encrypt_symlink);
|
EXPORT_SYMBOL_GPL(__fscrypt_encrypt_symlink);
|
||||||
|
|
||||||
@ -212,7 +221,7 @@ EXPORT_SYMBOL_GPL(__fscrypt_encrypt_symlink);
|
|||||||
* @inode: the symlink inode
|
* @inode: the symlink inode
|
||||||
* @caddr: the on-disk contents of the symlink
|
* @caddr: the on-disk contents of the symlink
|
||||||
* @max_size: size of @caddr buffer
|
* @max_size: size of @caddr buffer
|
||||||
* @done: if successful, will be set up to free the returned target
|
* @done: if successful, will be set up to free the returned target if needed
|
||||||
*
|
*
|
||||||
* If the symlink's encryption key is available, we decrypt its target.
|
* If the symlink's encryption key is available, we decrypt its target.
|
||||||
* Otherwise, we encode its target for presentation.
|
* Otherwise, we encode its target for presentation.
|
||||||
@ -227,12 +236,18 @@ const char *fscrypt_get_symlink(struct inode *inode, const void *caddr,
|
|||||||
{
|
{
|
||||||
const struct fscrypt_symlink_data *sd;
|
const struct fscrypt_symlink_data *sd;
|
||||||
struct fscrypt_str cstr, pstr;
|
struct fscrypt_str cstr, pstr;
|
||||||
|
bool has_key;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
/* This is for encrypted symlinks only */
|
/* This is for encrypted symlinks only */
|
||||||
if (WARN_ON(!IS_ENCRYPTED(inode)))
|
if (WARN_ON(!IS_ENCRYPTED(inode)))
|
||||||
return ERR_PTR(-EINVAL);
|
return ERR_PTR(-EINVAL);
|
||||||
|
|
||||||
|
/* If the decrypted target is already cached, just return it. */
|
||||||
|
pstr.name = READ_ONCE(inode->i_link);
|
||||||
|
if (pstr.name)
|
||||||
|
return pstr.name;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Try to set up the symlink's encryption key, but we can continue
|
* Try to set up the symlink's encryption key, but we can continue
|
||||||
* regardless of whether the key is available or not.
|
* regardless of whether the key is available or not.
|
||||||
@ -240,6 +255,7 @@ const char *fscrypt_get_symlink(struct inode *inode, const void *caddr,
|
|||||||
err = fscrypt_get_encryption_info(inode);
|
err = fscrypt_get_encryption_info(inode);
|
||||||
if (err)
|
if (err)
|
||||||
return ERR_PTR(err);
|
return ERR_PTR(err);
|
||||||
|
has_key = fscrypt_has_encryption_key(inode);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For historical reasons, encrypted symlink targets are prefixed with
|
* For historical reasons, encrypted symlink targets are prefixed with
|
||||||
@ -271,7 +287,17 @@ const char *fscrypt_get_symlink(struct inode *inode, const void *caddr,
|
|||||||
goto err_kfree;
|
goto err_kfree;
|
||||||
|
|
||||||
pstr.name[pstr.len] = '\0';
|
pstr.name[pstr.len] = '\0';
|
||||||
set_delayed_call(done, kfree_link, pstr.name);
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
if (!has_key ||
|
||||||
|
cmpxchg_release(&inode->i_link, NULL, pstr.name) != NULL)
|
||||||
|
set_delayed_call(done, kfree_link, pstr.name);
|
||||||
|
|
||||||
return pstr.name;
|
return pstr.name;
|
||||||
|
|
||||||
err_kfree:
|
err_kfree:
|
||||||
|
@ -584,9 +584,30 @@ out:
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL(fscrypt_get_encryption_info);
|
EXPORT_SYMBOL(fscrypt_get_encryption_info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fscrypt_put_encryption_info - free most of an inode's fscrypt data
|
||||||
|
*
|
||||||
|
* Free the inode's fscrypt_info. Filesystems must call this when the inode is
|
||||||
|
* being evicted. An RCU grace period need not have elapsed yet.
|
||||||
|
*/
|
||||||
void fscrypt_put_encryption_info(struct inode *inode)
|
void fscrypt_put_encryption_info(struct inode *inode)
|
||||||
{
|
{
|
||||||
put_crypt_info(inode->i_crypt_info);
|
put_crypt_info(inode->i_crypt_info);
|
||||||
inode->i_crypt_info = NULL;
|
inode->i_crypt_info = NULL;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(fscrypt_put_encryption_info);
|
EXPORT_SYMBOL(fscrypt_put_encryption_info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fscrypt_free_inode - free an inode's fscrypt data requiring RCU delay
|
||||||
|
*
|
||||||
|
* Free the inode's cached decrypted symlink target, if any. Filesystems must
|
||||||
|
* call this after an RCU grace period, just before they free the inode.
|
||||||
|
*/
|
||||||
|
void fscrypt_free_inode(struct inode *inode)
|
||||||
|
{
|
||||||
|
if (IS_ENCRYPTED(inode) && S_ISLNK(inode->i_mode)) {
|
||||||
|
kfree(inode->i_link);
|
||||||
|
inode->i_link = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(fscrypt_free_inode);
|
||||||
|
@ -1110,6 +1110,9 @@ static int ext4_drop_inode(struct inode *inode)
|
|||||||
static void ext4_i_callback(struct rcu_head *head)
|
static void ext4_i_callback(struct rcu_head *head)
|
||||||
{
|
{
|
||||||
struct inode *inode = container_of(head, struct inode, i_rcu);
|
struct inode *inode = container_of(head, struct inode, i_rcu);
|
||||||
|
|
||||||
|
fscrypt_free_inode(inode);
|
||||||
|
|
||||||
kmem_cache_free(ext4_inode_cachep, EXT4_I(inode));
|
kmem_cache_free(ext4_inode_cachep, EXT4_I(inode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1003,6 +1003,9 @@ static void f2fs_dirty_inode(struct inode *inode, int flags)
|
|||||||
static void f2fs_i_callback(struct rcu_head *head)
|
static void f2fs_i_callback(struct rcu_head *head)
|
||||||
{
|
{
|
||||||
struct inode *inode = container_of(head, struct inode, i_rcu);
|
struct inode *inode = container_of(head, struct inode, i_rcu);
|
||||||
|
|
||||||
|
fscrypt_free_inode(inode);
|
||||||
|
|
||||||
kmem_cache_free(f2fs_inode_cachep, F2FS_I(inode));
|
kmem_cache_free(f2fs_inode_cachep, F2FS_I(inode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +276,10 @@ static void ubifs_i_callback(struct rcu_head *head)
|
|||||||
{
|
{
|
||||||
struct inode *inode = container_of(head, struct inode, i_rcu);
|
struct inode *inode = container_of(head, struct inode, i_rcu);
|
||||||
struct ubifs_inode *ui = ubifs_inode(inode);
|
struct ubifs_inode *ui = ubifs_inode(inode);
|
||||||
|
|
||||||
kfree(ui->data);
|
kfree(ui->data);
|
||||||
|
fscrypt_free_inode(inode);
|
||||||
|
|
||||||
kmem_cache_free(ubifs_inode_slab, ui);
|
kmem_cache_free(ubifs_inode_slab, ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +128,7 @@ extern int fscrypt_inherit_context(struct inode *, struct inode *,
|
|||||||
/* keyinfo.c */
|
/* keyinfo.c */
|
||||||
extern int fscrypt_get_encryption_info(struct inode *);
|
extern int fscrypt_get_encryption_info(struct inode *);
|
||||||
extern void fscrypt_put_encryption_info(struct inode *);
|
extern void fscrypt_put_encryption_info(struct inode *);
|
||||||
|
extern void fscrypt_free_inode(struct inode *);
|
||||||
|
|
||||||
/* fname.c */
|
/* fname.c */
|
||||||
extern int fscrypt_setup_filename(struct inode *, const struct qstr *,
|
extern int fscrypt_setup_filename(struct inode *, const struct qstr *,
|
||||||
@ -341,6 +342,10 @@ static inline void fscrypt_put_encryption_info(struct inode *inode)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void fscrypt_free_inode(struct inode *inode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/* fname.c */
|
/* fname.c */
|
||||||
static inline int fscrypt_setup_filename(struct inode *dir,
|
static inline int fscrypt_setup_filename(struct inode *dir,
|
||||||
const struct qstr *iname,
|
const struct qstr *iname,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user