Skip to content

Commit

Permalink
fscrypt: cache decrypted symlink target in ->i_link
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Eric Biggers authored and Theodore Ts'o committed Apr 17, 2019
1 parent 4c4f7c1 commit 2c58d54
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 7 deletions.
40 changes: 33 additions & 7 deletions fs/crypto/hooks.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,21 +189,30 @@ int __fscrypt_encrypt_symlink(struct inode *inode, const char *target,
sd->len = cpu_to_le16(ciphertext_len);

err = fname_encrypt(inode, &iname, sd->encrypted_path, ciphertext_len);
if (err) {
if (!disk_link->name)
kfree(sd);
return err;
}
if (err)
goto err_free_sd;

/*
* Null-terminating the ciphertext doesn't make sense, but we still
* count the null terminator in the length, so we might as well
* initialize it just in case the filesystem writes it out.
*/
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)
disk_link->name = (unsigned char *)sd;
return 0;

err_free_sd:
if (!disk_link->name)
kfree(sd);
return err;
}
EXPORT_SYMBOL_GPL(__fscrypt_encrypt_symlink);

Expand All @@ -212,7 +221,7 @@ EXPORT_SYMBOL_GPL(__fscrypt_encrypt_symlink);
* @inode: the symlink inode
* @caddr: the on-disk contents of the symlink
* @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.
* Otherwise, we encode its target for presentation.
Expand All @@ -227,19 +236,26 @@ const char *fscrypt_get_symlink(struct inode *inode, const void *caddr,
{
const struct fscrypt_symlink_data *sd;
struct fscrypt_str cstr, pstr;
bool has_key;
int err;

/* This is for encrypted symlinks only */
if (WARN_ON(!IS_ENCRYPTED(inode)))
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
* regardless of whether the key is available or not.
*/
err = fscrypt_get_encryption_info(inode);
if (err)
return ERR_PTR(err);
has_key = fscrypt_has_encryption_key(inode);

/*
* For historical reasons, encrypted symlink targets are prefixed with
Expand Down Expand Up @@ -271,7 +287,17 @@ const char *fscrypt_get_symlink(struct inode *inode, const void *caddr,
goto err_kfree;

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;

err_kfree:
Expand Down
21 changes: 21 additions & 0 deletions fs/crypto/keyinfo.c
Original file line number Diff line number Diff line change
Expand Up @@ -584,9 +584,30 @@ int fscrypt_get_encryption_info(struct inode *inode)
}
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)
{
put_crypt_info(inode->i_crypt_info);
inode->i_crypt_info = NULL;
}
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);
3 changes: 3 additions & 0 deletions fs/ext4/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,9 @@ static int ext4_drop_inode(struct inode *inode)
static void ext4_i_callback(struct rcu_head *head)
{
struct inode *inode = container_of(head, struct inode, i_rcu);

fscrypt_free_inode(inode);

kmem_cache_free(ext4_inode_cachep, EXT4_I(inode));
}

Expand Down
3 changes: 3 additions & 0 deletions fs/f2fs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,9 @@ static void f2fs_dirty_inode(struct inode *inode, int flags)
static void f2fs_i_callback(struct rcu_head *head)
{
struct inode *inode = container_of(head, struct inode, i_rcu);

fscrypt_free_inode(inode);

kmem_cache_free(f2fs_inode_cachep, F2FS_I(inode));
}

Expand Down
3 changes: 3 additions & 0 deletions fs/ubifs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,10 @@ static void ubifs_i_callback(struct rcu_head *head)
{
struct inode *inode = container_of(head, struct inode, i_rcu);
struct ubifs_inode *ui = ubifs_inode(inode);

kfree(ui->data);
fscrypt_free_inode(inode);

kmem_cache_free(ubifs_inode_slab, ui);
}

Expand Down
5 changes: 5 additions & 0 deletions include/linux/fscrypt.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ extern int fscrypt_inherit_context(struct inode *, struct inode *,
/* keyinfo.c */
extern int fscrypt_get_encryption_info(struct inode *);
extern void fscrypt_put_encryption_info(struct inode *);
extern void fscrypt_free_inode(struct inode *);

/* fname.c */
extern int fscrypt_setup_filename(struct inode *, const struct qstr *,
Expand Down Expand Up @@ -341,6 +342,10 @@ static inline void fscrypt_put_encryption_info(struct inode *inode)
return;
}

static inline void fscrypt_free_inode(struct inode *inode)
{
}

/* fname.c */
static inline int fscrypt_setup_filename(struct inode *dir,
const struct qstr *iname,
Expand Down

0 comments on commit 2c58d54

Please sign in to comment.