diff options
| author | Al Viro <viro@zeniv.linux.org.uk> | 2014-04-19 12:30:58 -0400 | 
|---|---|---|
| committer | Al Viro <viro@zeniv.linux.org.uk> | 2014-04-19 12:30:58 -0400 | 
| commit | 22213318af7ae265bc6cd8aef2febbc2d69a2440 (patch) | |
| tree | bb9b6c5e25909555f5243d8983d07c96cfeb08a9 | |
| parent | c9eaa447e77efe77b7fa4c953bd62de8297fd6c5 (diff) | |
fix races between __d_instantiate() and checks of dentry flags
in non-lazy walk we need to be careful about dentry switching from
negative to positive - both ->d_flags and ->d_inode are updated,
and in some places we might see only one store.  The cases where
dentry has been obtained by dcache lookup with ->i_mutex held on
parent are safe - ->d_lock and ->i_mutex provide all the barriers
we need.  However, there are several places where we run into
trouble:
	* do_last() fetches ->d_inode, then checks ->d_flags and
assumes that inode won't be NULL unless d_is_negative() is true.
Race with e.g. creat() - we might have fetched the old value of
->d_inode (still NULL) and new value of ->d_flags (already not
DCACHE_MISS_TYPE).  Lin Ming has observed and reported the resulting
oops.
	* a bunch of places checks ->d_inode for being non-NULL,
then checks ->d_flags for "is it a symlink".  Race with symlink(2)
in case if our CPU sees ->d_inode update first - we see non-NULL
there, but ->d_flags still contains DCACHE_MISS_TYPE instead of
DCACHE_SYMLINK_TYPE.  Result: false negative on "should we follow
link here?", with subsequent unpleasantness.
Cc: stable@vger.kernel.org # 3.13 and 3.14 need that one
Reported-and-tested-by: Lin Ming <minggr@gmail.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
| -rw-r--r-- | fs/dcache.c | 3 | ||||
| -rw-r--r-- | fs/namei.c | 6 | 
2 files changed, 4 insertions, 5 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index 40707d88a945..494a9def5dce 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1647,8 +1647,7 @@ static void __d_instantiate(struct dentry *dentry, struct inode *inode)  	unsigned add_flags = d_flags_for_inode(inode);  	spin_lock(&dentry->d_lock); -	dentry->d_flags &= ~DCACHE_ENTRY_TYPE; -	dentry->d_flags |= add_flags; +	__d_set_type(dentry, add_flags);  	if (inode)  		hlist_add_head(&dentry->d_alias, &inode->i_dentry);  	dentry->d_inode = inode; diff --git a/fs/namei.c b/fs/namei.c index c6157c894fce..80168273396b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1542,7 +1542,7 @@ static inline int walk_component(struct nameidata *nd, struct path *path,  		inode = path->dentry->d_inode;  	}  	err = -ENOENT; -	if (!inode) +	if (!inode || d_is_negative(path->dentry))  		goto out_path_put;  	if (should_follow_link(path->dentry, follow)) { @@ -2249,7 +2249,7 @@ mountpoint_last(struct nameidata *nd, struct path *path)  	mutex_unlock(&dir->d_inode->i_mutex);  done: -	if (!dentry->d_inode) { +	if (!dentry->d_inode || d_is_negative(dentry)) {  		error = -ENOENT;  		dput(dentry);  		goto out; @@ -2994,7 +2994,7 @@ retry_lookup:  finish_lookup:  	/* we _can_ be in RCU mode here */  	error = -ENOENT; -	if (d_is_negative(path->dentry)) { +	if (!inode || d_is_negative(path->dentry)) {  		path_to_nameidata(path, nd);  		goto out;  	}  | 
