Skip to content

Commit 581ae68

Browse files
Al ViroJ. Bruce Fields
authored andcommitted
race in exportfs_decode_fh()
On Sat, Nov 02, 2019 at 06:08:42PM +0000, Al Viro wrote: > It is converging to a reasonably small and understandable surface, actually, > most of that being in core pathname resolution. Two big piles of nightmares > left to review - overlayfs and (somewhat surprisingly) setxattr call chains, > the latter due to IMA/EVM/LSM insanity... Oh, lovely - in exportfs_decode_fh() we have this: err = exportfs_get_name(mnt, target_dir, nbuf, result); if (!err) { inode_lock(target_dir->d_inode); nresult = lookup_one_len(nbuf, target_dir, strlen(nbuf)); inode_unlock(target_dir->d_inode); if (!IS_ERR(nresult)) { if (nresult->d_inode) { dput(result); result = nresult; } else dput(nresult); } } We have derived the parent from fhandle, we have a disconnected dentry for child, we go look for the name. We even find it. Now, we want to look it up. And some bastard goes and unlinks it, just as we are trying to lock the parent. We do a lookup, and get a negative dentry. Then we unlock the parent... and some other bastard does e.g. mkdir with the same name. OK, nresult->d_inode is not NULL (anymore). It has fuck-all to do with the original fhandle (different inumber, etc.) but we happily accept it. Even better, we have no barriers between our check and nresult becoming positive. IOW, having observed non-NULL ->d_inode doesn't give us enough - e.g. we might still see the old ->d_flags value, from back when ->d_inode used to be NULL. On something like alpha we also have no promises that we'll observe anything about the fields of nresult->d_inode, but ->d_flags alone is enough for fun. The callers can't e.g. expect d_is_reg() et.al. to match the reality. This is obviously bogus. And the fix is obvious: check that nresult->d_inode is equal to result->d_inode before unlocking the parent. Note that we'd *already* had the original result and all of its aliases rejected by the 'acceptable' predicate, so if nresult doesn't supply us a better alias, we are SOL. Does anyone see objections to the following patch? Christoph, that seems to be your code; am I missing something subtle here? AFAICS, that goes back to 2007 or so... Signed-off-by: Al Viro <[email protected]> Signed-off-by: J. Bruce Fields <[email protected]>
1 parent 2a67803 commit 581ae68

File tree

1 file changed

+19
-12
lines changed

1 file changed

+19
-12
lines changed

fs/exportfs/expfs.c

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -519,26 +519,33 @@ struct dentry *exportfs_decode_fh(struct vfsmount *mnt, struct fid *fid,
519519
* inode is actually connected to the parent.
520520
*/
521521
err = exportfs_get_name(mnt, target_dir, nbuf, result);
522-
if (!err) {
523-
inode_lock(target_dir->d_inode);
524-
nresult = lookup_one_len(nbuf, target_dir,
525-
strlen(nbuf));
526-
inode_unlock(target_dir->d_inode);
527-
if (!IS_ERR(nresult)) {
528-
if (nresult->d_inode) {
529-
dput(result);
530-
result = nresult;
531-
} else
532-
dput(nresult);
533-
}
522+
if (err) {
523+
dput(target_dir);
524+
goto err_result;
534525
}
535526

527+
inode_lock(target_dir->d_inode);
528+
nresult = lookup_one_len(nbuf, target_dir, strlen(nbuf));
529+
if (!IS_ERR(nresult)) {
530+
if (unlikely(nresult->d_inode != result->d_inode)) {
531+
dput(nresult);
532+
nresult = ERR_PTR(-ESTALE);
533+
}
534+
}
535+
inode_unlock(target_dir->d_inode);
536536
/*
537537
* At this point we are done with the parent, but it's pinned
538538
* by the child dentry anyway.
539539
*/
540540
dput(target_dir);
541541

542+
if (IS_ERR(nresult)) {
543+
err = PTR_ERR(nresult);
544+
goto err_result;
545+
}
546+
dput(result);
547+
result = nresult;
548+
542549
/*
543550
* And finally make sure the dentry is actually acceptable
544551
* to NFSD.

0 commit comments

Comments
 (0)