eCryptfs: Prevent file create race condition

The file creation path prematurely called d_instantiate() and
unlock_new_inode() before the eCryptfs inode info was fully
allocated and initialized and before the eCryptfs metadata was written
to the lower file.

This could result in race conditions in subsequent file and inode
operations leading to unexpected error conditions or a null pointer
dereference while attempting to use the unallocated memory.

https://launchpad.net/bugs/813146

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Cc: stable@kernel.org
This commit is contained in:
Tyler Hicks 2011-11-21 17:31:02 -06:00
parent 6fe4c6d466
commit b59db43ad4
3 changed files with 46 additions and 33 deletions

View File

@ -967,7 +967,7 @@ static void ecryptfs_set_default_crypt_stat_vals(
/** /**
* ecryptfs_new_file_context * ecryptfs_new_file_context
* @ecryptfs_dentry: The eCryptfs dentry * @ecryptfs_inode: The eCryptfs inode
* *
* If the crypto context for the file has not yet been established, * If the crypto context for the file has not yet been established,
* this is where we do that. Establishing a new crypto context * this is where we do that. Establishing a new crypto context
@ -984,13 +984,13 @@ static void ecryptfs_set_default_crypt_stat_vals(
* *
* Returns zero on success; non-zero otherwise * Returns zero on success; non-zero otherwise
*/ */
int ecryptfs_new_file_context(struct dentry *ecryptfs_dentry) int ecryptfs_new_file_context(struct inode *ecryptfs_inode)
{ {
struct ecryptfs_crypt_stat *crypt_stat = struct ecryptfs_crypt_stat *crypt_stat =
&ecryptfs_inode_to_private(ecryptfs_dentry->d_inode)->crypt_stat; &ecryptfs_inode_to_private(ecryptfs_inode)->crypt_stat;
struct ecryptfs_mount_crypt_stat *mount_crypt_stat = struct ecryptfs_mount_crypt_stat *mount_crypt_stat =
&ecryptfs_superblock_to_private( &ecryptfs_superblock_to_private(
ecryptfs_dentry->d_sb)->mount_crypt_stat; ecryptfs_inode->i_sb)->mount_crypt_stat;
int cipher_name_len; int cipher_name_len;
int rc = 0; int rc = 0;
@ -1299,12 +1299,12 @@ static int ecryptfs_write_headers_virt(char *page_virt, size_t max,
} }
static int static int
ecryptfs_write_metadata_to_contents(struct dentry *ecryptfs_dentry, ecryptfs_write_metadata_to_contents(struct inode *ecryptfs_inode,
char *virt, size_t virt_len) char *virt, size_t virt_len)
{ {
int rc; int rc;
rc = ecryptfs_write_lower(ecryptfs_dentry->d_inode, virt, rc = ecryptfs_write_lower(ecryptfs_inode, virt,
0, virt_len); 0, virt_len);
if (rc < 0) if (rc < 0)
printk(KERN_ERR "%s: Error attempting to write header " printk(KERN_ERR "%s: Error attempting to write header "
@ -1338,7 +1338,8 @@ static unsigned long ecryptfs_get_zeroed_pages(gfp_t gfp_mask,
/** /**
* ecryptfs_write_metadata * ecryptfs_write_metadata
* @ecryptfs_dentry: The eCryptfs dentry * @ecryptfs_dentry: The eCryptfs dentry, which should be negative
* @ecryptfs_inode: The newly created eCryptfs inode
* *
* Write the file headers out. This will likely involve a userspace * Write the file headers out. This will likely involve a userspace
* callout, in which the session key is encrypted with one or more * callout, in which the session key is encrypted with one or more
@ -1348,10 +1349,11 @@ static unsigned long ecryptfs_get_zeroed_pages(gfp_t gfp_mask,
* *
* Returns zero on success; non-zero on error * Returns zero on success; non-zero on error
*/ */
int ecryptfs_write_metadata(struct dentry *ecryptfs_dentry) int ecryptfs_write_metadata(struct dentry *ecryptfs_dentry,
struct inode *ecryptfs_inode)
{ {
struct ecryptfs_crypt_stat *crypt_stat = struct ecryptfs_crypt_stat *crypt_stat =
&ecryptfs_inode_to_private(ecryptfs_dentry->d_inode)->crypt_stat; &ecryptfs_inode_to_private(ecryptfs_inode)->crypt_stat;
unsigned int order; unsigned int order;
char *virt; char *virt;
size_t virt_len; size_t virt_len;
@ -1391,7 +1393,7 @@ int ecryptfs_write_metadata(struct dentry *ecryptfs_dentry)
rc = ecryptfs_write_metadata_to_xattr(ecryptfs_dentry, virt, rc = ecryptfs_write_metadata_to_xattr(ecryptfs_dentry, virt,
size); size);
else else
rc = ecryptfs_write_metadata_to_contents(ecryptfs_dentry, virt, rc = ecryptfs_write_metadata_to_contents(ecryptfs_inode, virt,
virt_len); virt_len);
if (rc) { if (rc) {
printk(KERN_ERR "%s: Error writing metadata out to lower file; " printk(KERN_ERR "%s: Error writing metadata out to lower file; "

View File

@ -584,9 +584,10 @@ int ecryptfs_init_crypt_ctx(struct ecryptfs_crypt_stat *crypt_stat);
int ecryptfs_write_inode_size_to_metadata(struct inode *ecryptfs_inode); int ecryptfs_write_inode_size_to_metadata(struct inode *ecryptfs_inode);
int ecryptfs_encrypt_page(struct page *page); int ecryptfs_encrypt_page(struct page *page);
int ecryptfs_decrypt_page(struct page *page); int ecryptfs_decrypt_page(struct page *page);
int ecryptfs_write_metadata(struct dentry *ecryptfs_dentry); int ecryptfs_write_metadata(struct dentry *ecryptfs_dentry,
struct inode *ecryptfs_inode);
int ecryptfs_read_metadata(struct dentry *ecryptfs_dentry); int ecryptfs_read_metadata(struct dentry *ecryptfs_dentry);
int ecryptfs_new_file_context(struct dentry *ecryptfs_dentry); int ecryptfs_new_file_context(struct inode *ecryptfs_inode);
void ecryptfs_write_crypt_stat_flags(char *page_virt, void ecryptfs_write_crypt_stat_flags(char *page_virt,
struct ecryptfs_crypt_stat *crypt_stat, struct ecryptfs_crypt_stat *crypt_stat,
size_t *written); size_t *written);

View File

@ -172,22 +172,23 @@ ecryptfs_create_underlying_file(struct inode *lower_dir_inode,
* it. It will also update the eCryptfs directory inode to mimic the * it. It will also update the eCryptfs directory inode to mimic the
* stat of the lower directory inode. * stat of the lower directory inode.
* *
* Returns zero on success; non-zero on error condition * Returns the new eCryptfs inode on success; an ERR_PTR on error condition
*/ */
static int static struct inode *
ecryptfs_do_create(struct inode *directory_inode, ecryptfs_do_create(struct inode *directory_inode,
struct dentry *ecryptfs_dentry, int mode) struct dentry *ecryptfs_dentry, int mode)
{ {
int rc; int rc;
struct dentry *lower_dentry; struct dentry *lower_dentry;
struct dentry *lower_dir_dentry; struct dentry *lower_dir_dentry;
struct inode *inode;
lower_dentry = ecryptfs_dentry_to_lower(ecryptfs_dentry); lower_dentry = ecryptfs_dentry_to_lower(ecryptfs_dentry);
lower_dir_dentry = lock_parent(lower_dentry); lower_dir_dentry = lock_parent(lower_dentry);
if (IS_ERR(lower_dir_dentry)) { if (IS_ERR(lower_dir_dentry)) {
ecryptfs_printk(KERN_ERR, "Error locking directory of " ecryptfs_printk(KERN_ERR, "Error locking directory of "
"dentry\n"); "dentry\n");
rc = PTR_ERR(lower_dir_dentry); inode = ERR_CAST(lower_dir_dentry);
goto out; goto out;
} }
rc = ecryptfs_create_underlying_file(lower_dir_dentry->d_inode, rc = ecryptfs_create_underlying_file(lower_dir_dentry->d_inode,
@ -195,20 +196,19 @@ ecryptfs_do_create(struct inode *directory_inode,
if (rc) { if (rc) {
printk(KERN_ERR "%s: Failure to create dentry in lower fs; " printk(KERN_ERR "%s: Failure to create dentry in lower fs; "
"rc = [%d]\n", __func__, rc); "rc = [%d]\n", __func__, rc);
inode = ERR_PTR(rc);
goto out_lock; goto out_lock;
} }
rc = ecryptfs_interpose(lower_dentry, ecryptfs_dentry, inode = __ecryptfs_get_inode(lower_dentry->d_inode,
directory_inode->i_sb); directory_inode->i_sb);
if (rc) { if (IS_ERR(inode))
ecryptfs_printk(KERN_ERR, "Failure in ecryptfs_interpose\n");
goto out_lock; goto out_lock;
}
fsstack_copy_attr_times(directory_inode, lower_dir_dentry->d_inode); fsstack_copy_attr_times(directory_inode, lower_dir_dentry->d_inode);
fsstack_copy_inode_size(directory_inode, lower_dir_dentry->d_inode); fsstack_copy_inode_size(directory_inode, lower_dir_dentry->d_inode);
out_lock: out_lock:
unlock_dir(lower_dir_dentry); unlock_dir(lower_dir_dentry);
out: out:
return rc; return inode;
} }
/** /**
@ -219,26 +219,26 @@ out:
* *
* Returns zero on success * Returns zero on success
*/ */
static int ecryptfs_initialize_file(struct dentry *ecryptfs_dentry) static int ecryptfs_initialize_file(struct dentry *ecryptfs_dentry,
struct inode *ecryptfs_inode)
{ {
struct ecryptfs_crypt_stat *crypt_stat = struct ecryptfs_crypt_stat *crypt_stat =
&ecryptfs_inode_to_private(ecryptfs_dentry->d_inode)->crypt_stat; &ecryptfs_inode_to_private(ecryptfs_inode)->crypt_stat;
int rc = 0; int rc = 0;
if (S_ISDIR(ecryptfs_dentry->d_inode->i_mode)) { if (S_ISDIR(ecryptfs_inode->i_mode)) {
ecryptfs_printk(KERN_DEBUG, "This is a directory\n"); ecryptfs_printk(KERN_DEBUG, "This is a directory\n");
crypt_stat->flags &= ~(ECRYPTFS_ENCRYPTED); crypt_stat->flags &= ~(ECRYPTFS_ENCRYPTED);
goto out; goto out;
} }
ecryptfs_printk(KERN_DEBUG, "Initializing crypto context\n"); ecryptfs_printk(KERN_DEBUG, "Initializing crypto context\n");
rc = ecryptfs_new_file_context(ecryptfs_dentry); rc = ecryptfs_new_file_context(ecryptfs_inode);
if (rc) { if (rc) {
ecryptfs_printk(KERN_ERR, "Error creating new file " ecryptfs_printk(KERN_ERR, "Error creating new file "
"context; rc = [%d]\n", rc); "context; rc = [%d]\n", rc);
goto out; goto out;
} }
rc = ecryptfs_get_lower_file(ecryptfs_dentry, rc = ecryptfs_get_lower_file(ecryptfs_dentry, ecryptfs_inode);
ecryptfs_dentry->d_inode);
if (rc) { if (rc) {
printk(KERN_ERR "%s: Error attempting to initialize " printk(KERN_ERR "%s: Error attempting to initialize "
"the lower file for the dentry with name " "the lower file for the dentry with name "
@ -246,10 +246,10 @@ static int ecryptfs_initialize_file(struct dentry *ecryptfs_dentry)
ecryptfs_dentry->d_name.name, rc); ecryptfs_dentry->d_name.name, rc);
goto out; goto out;
} }
rc = ecryptfs_write_metadata(ecryptfs_dentry); rc = ecryptfs_write_metadata(ecryptfs_dentry, ecryptfs_inode);
if (rc) if (rc)
printk(KERN_ERR "Error writing headers; rc = [%d]\n", rc); printk(KERN_ERR "Error writing headers; rc = [%d]\n", rc);
ecryptfs_put_lower_file(ecryptfs_dentry->d_inode); ecryptfs_put_lower_file(ecryptfs_inode);
out: out:
return rc; return rc;
} }
@ -269,18 +269,28 @@ static int
ecryptfs_create(struct inode *directory_inode, struct dentry *ecryptfs_dentry, ecryptfs_create(struct inode *directory_inode, struct dentry *ecryptfs_dentry,
int mode, struct nameidata *nd) int mode, struct nameidata *nd)
{ {
struct inode *ecryptfs_inode;
int rc; int rc;
/* ecryptfs_do_create() calls ecryptfs_interpose() */ ecryptfs_inode = ecryptfs_do_create(directory_inode, ecryptfs_dentry,
rc = ecryptfs_do_create(directory_inode, ecryptfs_dentry, mode); mode);
if (unlikely(rc)) { if (unlikely(IS_ERR(ecryptfs_inode))) {
ecryptfs_printk(KERN_WARNING, "Failed to create file in" ecryptfs_printk(KERN_WARNING, "Failed to create file in"
"lower filesystem\n"); "lower filesystem\n");
rc = PTR_ERR(ecryptfs_inode);
goto out; goto out;
} }
/* At this point, a file exists on "disk"; we need to make sure /* At this point, a file exists on "disk"; we need to make sure
* that this on disk file is prepared to be an ecryptfs file */ * that this on disk file is prepared to be an ecryptfs file */
rc = ecryptfs_initialize_file(ecryptfs_dentry); rc = ecryptfs_initialize_file(ecryptfs_dentry, ecryptfs_inode);
if (rc) {
drop_nlink(ecryptfs_inode);
unlock_new_inode(ecryptfs_inode);
iput(ecryptfs_inode);
goto out;
}
d_instantiate(ecryptfs_dentry, ecryptfs_inode);
unlock_new_inode(ecryptfs_inode);
out: out:
return rc; return rc;
} }