diff options
| author | Al Viro <viro@zeniv.linux.org.uk> | 2011-07-16 23:37:20 -0400 | 
|---|---|---|
| committer | Al Viro <viro@zeniv.linux.org.uk> | 2011-07-16 23:37:20 -0400 | 
| commit | dc137bf553dbb6855bd7efc34fedcd03102455f7 (patch) | |
| tree | ab1fefc7da26bb86655a28651c4eafb56d741ea1 /fs/cifs | |
| parent | 3110df800c4de2724624d46e6bed27efc5e9a707 (diff) | |
| download | linux-dc137bf553dbb6855bd7efc34fedcd03102455f7.tar.bz2 | |
cifs: build_path_from_dentry() race fix
deal with d_move() races properly; rename_lock read-retry loop,
rcu_read_lock() held while walking to root, d_lock held over
subtraction from namelen and copying the component to stabilize
->d_name.
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs/cifs')
| -rw-r--r-- | fs/cifs/dir.c | 13 | 
1 files changed, 12 insertions, 1 deletions
diff --git a/fs/cifs/dir.c b/fs/cifs/dir.c index 81914df47ef1..fa8c21d913bc 100644 --- a/fs/cifs/dir.c +++ b/fs/cifs/dir.c @@ -55,6 +55,7 @@ build_path_from_dentry(struct dentry *direntry)  	char dirsep;  	struct cifs_sb_info *cifs_sb = CIFS_SB(direntry->d_sb);  	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); +	unsigned seq;  	if (direntry == NULL)  		return NULL;  /* not much we can do if dentry is freed and @@ -68,22 +69,29 @@ build_path_from_dentry(struct dentry *direntry)  		dfsplen = 0;  cifs_bp_rename_retry:  	namelen = dfsplen; +	seq = read_seqbegin(&rename_lock); +	rcu_read_lock();  	for (temp = direntry; !IS_ROOT(temp);) {  		namelen += (1 + temp->d_name.len);  		temp = temp->d_parent;  		if (temp == NULL) {  			cERROR(1, "corrupt dentry"); +			rcu_read_unlock();  			return NULL;  		}  	} +	rcu_read_unlock();  	full_path = kmalloc(namelen+1, GFP_KERNEL);  	if (full_path == NULL)  		return full_path;  	full_path[namelen] = 0;	/* trailing null */ +	rcu_read_lock();  	for (temp = direntry; !IS_ROOT(temp);) { +		spin_lock(&temp->d_lock);  		namelen -= 1 + temp->d_name.len;  		if (namelen < 0) { +			spin_unlock(&temp->d_lock);  			break;  		} else {  			full_path[namelen] = dirsep; @@ -91,14 +99,17 @@ cifs_bp_rename_retry:  				temp->d_name.len);  			cFYI(0, "name: %s", full_path + namelen);  		} +		spin_unlock(&temp->d_lock);  		temp = temp->d_parent;  		if (temp == NULL) {  			cERROR(1, "corrupt dentry"); +			rcu_read_unlock();  			kfree(full_path);  			return NULL;  		}  	} -	if (namelen != dfsplen) { +	rcu_read_unlock(); +	if (namelen != dfsplen || read_seqretry(&rename_lock, seq)) {  		cERROR(1, "did not end path lookup where expected namelen is %d",  			namelen);  		/* presumably this is only possible if racing with a rename  |