aboutsummaryrefslogtreecommitdiffstats
path: root/fs/dcache.c
diff options
context:
space:
mode:
authorNeilBrown <neilb@suse.de>2006-10-04 05:16:16 -0400
committerLinus Torvalds <torvalds@g5.osdl.org>2006-10-04 10:55:21 -0400
commit21c0d8fdd95024ffa708a938099148b8f1078d46 (patch)
treee812554e916f1fb7d9410640235fb7954d2a25b7 /fs/dcache.c
parent44c556000a31e8079cfbb9a42a7edb93ca6b589a (diff)
[PATCH] knfsd: close a race-opportunity in d_splice_alias
There is a possible race in d_splice_alias. Though __d_find_alias(inode, 1) will only return a dentry with DCACHE_DISCONNECTED set, it is possible for it to get cleared before the BUG_ON, and it is is not possible to lock against that. There are a couple of problems here. Firstly, the code doesn't match the comment. The comment describes a 'disconnected' dentry as being IS_ROOT as well as DCACHE_DISCONNECTED, however there is not testing of IS_ROOT anythere. A dentry is marked DCACHE_DISCONNECTED when allocated with d_alloc_anon, and remains DCACHE_DISCONNECTED while a path is built up towards the root. So a dentry can have a valid name and a valid parent and even grandparent, but will still be DCACHE_DISCONNECTED until a path to the root is created. Once the path to the root is complete, everything in the path gets DCACHE_DISCONNECTED cleared. So the fact that DCACHE_DISCONNECTED isn't enough to say that a dentry is free to be spliced in with a given name. This can only be allowed if the dentry does not yet have a name, so the IS_ROOT test is needed too. However even adding that test to __d_find_alias isn't enough. As d_splice_alias drops dcache_lock before calling d_move to perform the splice, it could race with another thread calling d_splice_alias to splice the inode in with a different name in a different part of the tree (in the case where a file has hard links). So that splicing code is only really safe for directories (as we know that directories only have one link). For directories, the caller of d_splice_alias will be holding i_mutex on the (unique) parent so there is no room for a race. A consequence of this is that a non-directory will never benefit from being spliced into a pre-exisiting dentry, but that isn't a problem. It is perfectly OK for a non-directory to have multiple dentries, some anonymous, some not. And the comment for d_splice_alias says that it only happens for directories anyway. Signed-off-by: Neil Brown <neilb@suse.de> Cc: Christoph Hellwig <hch@lst.de> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Dipankar Sarma <dipankar@in.ibm.com> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'fs/dcache.c')
-rw-r--r--fs/dcache.c9
1 files changed, 5 insertions, 4 deletions
diff --git a/fs/dcache.c b/fs/dcache.c
index fc2faa44f8d1..2355bddad8de 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -291,9 +291,9 @@ struct dentry * dget_locked(struct dentry *dentry)
291 * it can be unhashed only if it has no children, or if it is the root 291 * it can be unhashed only if it has no children, or if it is the root
292 * of a filesystem. 292 * of a filesystem.
293 * 293 *
294 * If the inode has a DCACHE_DISCONNECTED alias, then prefer 294 * If the inode has an IS_ROOT, DCACHE_DISCONNECTED alias, then prefer
295 * any other hashed alias over that one unless @want_discon is set, 295 * any other hashed alias over that one unless @want_discon is set,
296 * in which case only return a DCACHE_DISCONNECTED alias. 296 * in which case only return an IS_ROOT, DCACHE_DISCONNECTED alias.
297 */ 297 */
298 298
299static struct dentry * __d_find_alias(struct inode *inode, int want_discon) 299static struct dentry * __d_find_alias(struct inode *inode, int want_discon)
@@ -309,7 +309,8 @@ static struct dentry * __d_find_alias(struct inode *inode, int want_discon)
309 prefetch(next); 309 prefetch(next);
310 alias = list_entry(tmp, struct dentry, d_alias); 310 alias = list_entry(tmp, struct dentry, d_alias);
311 if (S_ISDIR(inode->i_mode) || !d_unhashed(alias)) { 311 if (S_ISDIR(inode->i_mode) || !d_unhashed(alias)) {
312 if (alias->d_flags & DCACHE_DISCONNECTED) 312 if (IS_ROOT(alias) &&
313 (alias->d_flags & DCACHE_DISCONNECTED))
313 discon_alias = alias; 314 discon_alias = alias;
314 else if (!want_discon) { 315 else if (!want_discon) {
315 __dget_locked(alias); 316 __dget_locked(alias);
@@ -1004,7 +1005,7 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)
1004{ 1005{
1005 struct dentry *new = NULL; 1006 struct dentry *new = NULL;
1006 1007
1007 if (inode) { 1008 if (inode && S_ISDIR(inode->i_mode)) {
1008 spin_lock(&dcache_lock); 1009 spin_lock(&dcache_lock);
1009 new = __d_find_alias(inode, 1); 1010 new = __d_find_alias(inode, 1);
1010 if (new) { 1011 if (new) {