diff options
Diffstat (limited to 'fs/crypto/hooks.c')
| -rw-r--r-- | fs/crypto/hooks.c | 32 | 
1 files changed, 26 insertions, 6 deletions
diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c index 104771c3d3f6..d8d5049b8fe1 100644 --- a/fs/crypto/hooks.c +++ b/fs/crypto/hooks.c @@ -30,21 +30,41 @@  int fscrypt_file_open(struct inode *inode, struct file *filp)  {  	int err; -	struct dentry *dir; +	struct dentry *dentry, *dentry_parent; +	struct inode *inode_parent;  	err = fscrypt_require_key(inode);  	if (err)  		return err; -	dir = dget_parent(file_dentry(filp)); -	if (IS_ENCRYPTED(d_inode(dir)) && -	    !fscrypt_has_permitted_context(d_inode(dir), inode)) { +	dentry = file_dentry(filp); + +	/* +	 * Getting a reference to the parent dentry is needed for the actual +	 * encryption policy comparison, but it's expensive on multi-core +	 * systems.  Since this function runs on unencrypted files too, start +	 * with a lightweight RCU-mode check for the parent directory being +	 * unencrypted (in which case it's fine for the child to be either +	 * unencrypted, or encrypted with any policy).  Only continue on to the +	 * full policy check if the parent directory is actually encrypted. +	 */ +	rcu_read_lock(); +	dentry_parent = READ_ONCE(dentry->d_parent); +	inode_parent = d_inode_rcu(dentry_parent); +	if (inode_parent != NULL && !IS_ENCRYPTED(inode_parent)) { +		rcu_read_unlock(); +		return 0; +	} +	rcu_read_unlock(); + +	dentry_parent = dget_parent(dentry); +	if (!fscrypt_has_permitted_context(d_inode(dentry_parent), inode)) {  		fscrypt_warn(inode,  			     "Inconsistent encryption context (parent directory: %lu)", -			     d_inode(dir)->i_ino); +			     d_inode(dentry_parent)->i_ino);  		err = -EPERM;  	} -	dput(dir); +	dput(dentry_parent);  	return err;  }  EXPORT_SYMBOL_GPL(fscrypt_file_open);  |