fscrypt: add fscrypt_is_nokey_name()

commit 159e1de201b6fca10bfec50405a3b53a561096a8 upstream.

It's possible to create a duplicate filename in an encrypted directory
by creating a file concurrently with adding the encryption key.

Specifically, sys_open(O_CREAT) (or sys_mkdir(), sys_mknod(), or
sys_symlink()) can lookup the target filename while the directory's
encryption key hasn't been added yet, resulting in a negative no-key
dentry.  The VFS then calls ->create() (or ->mkdir(), ->mknod(), or
->symlink()) because the dentry is negative.  Normally, ->create() would
return -ENOKEY due to the directory's key being unavailable.  However,
if the key was added between the dentry lookup and ->create(), then the
filesystem will go ahead and try to create the file.

If the target filename happens to already exist as a normal name (not a
no-key name), a duplicate filename may be added to the directory.

In order to fix this, we need to fix the filesystems to prevent
->create(), ->mkdir(), ->mknod(), and ->symlink() on no-key names.
(->rename() and ->link() need it too, but those are already handled
correctly by fscrypt_prepare_rename() and fscrypt_prepare_link().)

In preparation for this, add a helper function fscrypt_is_nokey_name()
that filesystems can use to do this check.  Use this helper function for
the existing checks that fs/crypto/ does for rename and link.

Cc: [email protected]
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Eric Biggers <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>
This commit is contained in:
Eric Biggers 2020-12-28 10:54:30 -08:00 • committed by Greg Kroah-Hartman
commit 34f000524d
2 changed files with 39 additions and 5 deletions

View file

@ -58,8 +58,8 @@ int __fscrypt_prepare_link(struct inode *inode, struct inode *dir,
if (err)
return err;
/* ... in case we looked up ciphertext name before key was added */
if (dentry->d_flags & DCACHE_ENCRYPTED_NAME)
/* ... in case we looked up no-key name before key was added */
if (fscrypt_is_nokey_name(dentry))
return -ENOKEY;
if (!fscrypt_has_permitted_context(dir, inode))
@ -83,9 +83,9 @@ int __fscrypt_prepare_rename(struct inode *old_dir, struct dentry *old_dentry,
if (err)
return err;
/* ... in case we looked up ciphertext name(s) before key was added */
if ((old_dentry->d_flags | new_dentry->d_flags) &
DCACHE_ENCRYPTED_NAME)
/* ... in case we looked up no-key name(s) before key was added */
if (fscrypt_is_nokey_name(old_dentry) ||
fscrypt_is_nokey_name(new_dentry))
return -ENOKEY;
if (old_dir != new_dir) {