diff options
author | NeilBrown <neilb@suse.com> | 2017-11-09 23:45:41 -0500 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2017-12-28 14:12:09 -0500 |
commit | 61647823aa920e395afcce4b57c32afb51456cab (patch) | |
tree | c8bbc4cfa1a8e95441de334be35bdf63d2bf0e52 | |
parent | f1ee616214cb22410e939d963bbb2349c2570f02 (diff) |
VFS: close race between getcwd() and d_move()
d_move() will call __d_drop() and then __d_rehash()
on the dentry being moved. This creates a small window
when the dentry appears to be unhashed. Many tests
of d_unhashed() are made under ->d_lock and so are safe
from racing with this window, but some aren't.
In particular, getcwd() calls d_unlinked() (which calls
d_unhashed()) without d_lock protection, so it can race.
This races has been seen in practice with lustre, which uses d_move() as
part of name lookup. See:
https://jira.hpdd.intel.com/browse/LU-9735
It could race with a regular rename(), and result in ENOENT instead
of either the 'before' or 'after' name.
The race can be demonstrated with a simple program which
has two threads, one renaming a directory back and forth
while another calls getcwd() within that directory: it should never
fail, but does. See:
https://patchwork.kernel.org/patch/9455345/
We could fix this race by taking d_lock and rechecking when
d_unhashed() reports true. Alternately when can remove the window,
which is the approach this patch takes.
___d_drop() is introduce which does *not* clear d_hash.pprev
so the dentry still appears to be hashed. __d_drop() calls
___d_drop(), then clears d_hash.pprev.
__d_move() now uses ___d_drop() and only clears d_hash.pprev
when not rehashing.
Signed-off-by: NeilBrown <neilb@suse.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
-rw-r--r-- | fs/dcache.c | 23 |
1 files changed, 16 insertions, 7 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index 17e6b84b9656..cab211e13744 100644 --- a/fs/dcache.c +++ b/fs/dcache.c | |||
@@ -467,9 +467,11 @@ static void dentry_lru_add(struct dentry *dentry) | |||
467 | * d_drop() is used mainly for stuff that wants to invalidate a dentry for some | 467 | * d_drop() is used mainly for stuff that wants to invalidate a dentry for some |
468 | * reason (NFS timeouts or autofs deletes). | 468 | * reason (NFS timeouts or autofs deletes). |
469 | * | 469 | * |
470 | * __d_drop requires dentry->d_lock. | 470 | * __d_drop requires dentry->d_lock |
471 | * ___d_drop doesn't mark dentry as "unhashed" | ||
472 | * (dentry->d_hash.pprev will be LIST_POISON2, not NULL). | ||
471 | */ | 473 | */ |
472 | void __d_drop(struct dentry *dentry) | 474 | static void ___d_drop(struct dentry *dentry) |
473 | { | 475 | { |
474 | if (!d_unhashed(dentry)) { | 476 | if (!d_unhashed(dentry)) { |
475 | struct hlist_bl_head *b; | 477 | struct hlist_bl_head *b; |
@@ -485,12 +487,17 @@ void __d_drop(struct dentry *dentry) | |||
485 | 487 | ||
486 | hlist_bl_lock(b); | 488 | hlist_bl_lock(b); |
487 | __hlist_bl_del(&dentry->d_hash); | 489 | __hlist_bl_del(&dentry->d_hash); |
488 | dentry->d_hash.pprev = NULL; | ||
489 | hlist_bl_unlock(b); | 490 | hlist_bl_unlock(b); |
490 | /* After this call, in-progress rcu-walk path lookup will fail. */ | 491 | /* After this call, in-progress rcu-walk path lookup will fail. */ |
491 | write_seqcount_invalidate(&dentry->d_seq); | 492 | write_seqcount_invalidate(&dentry->d_seq); |
492 | } | 493 | } |
493 | } | 494 | } |
495 | |||
496 | void __d_drop(struct dentry *dentry) | ||
497 | { | ||
498 | ___d_drop(dentry); | ||
499 | dentry->d_hash.pprev = NULL; | ||
500 | } | ||
494 | EXPORT_SYMBOL(__d_drop); | 501 | EXPORT_SYMBOL(__d_drop); |
495 | 502 | ||
496 | void d_drop(struct dentry *dentry) | 503 | void d_drop(struct dentry *dentry) |
@@ -2382,7 +2389,7 @@ EXPORT_SYMBOL(d_delete); | |||
2382 | static void __d_rehash(struct dentry *entry) | 2389 | static void __d_rehash(struct dentry *entry) |
2383 | { | 2390 | { |
2384 | struct hlist_bl_head *b = d_hash(entry->d_name.hash); | 2391 | struct hlist_bl_head *b = d_hash(entry->d_name.hash); |
2385 | BUG_ON(!d_unhashed(entry)); | 2392 | |
2386 | hlist_bl_lock(b); | 2393 | hlist_bl_lock(b); |
2387 | hlist_bl_add_head_rcu(&entry->d_hash, b); | 2394 | hlist_bl_add_head_rcu(&entry->d_hash, b); |
2388 | hlist_bl_unlock(b); | 2395 | hlist_bl_unlock(b); |
@@ -2817,9 +2824,9 @@ static void __d_move(struct dentry *dentry, struct dentry *target, | |||
2817 | write_seqcount_begin_nested(&target->d_seq, DENTRY_D_LOCK_NESTED); | 2824 | write_seqcount_begin_nested(&target->d_seq, DENTRY_D_LOCK_NESTED); |
2818 | 2825 | ||
2819 | /* unhash both */ | 2826 | /* unhash both */ |
2820 | /* __d_drop does write_seqcount_barrier, but they're OK to nest. */ | 2827 | /* ___d_drop does write_seqcount_barrier, but they're OK to nest. */ |
2821 | __d_drop(dentry); | 2828 | ___d_drop(dentry); |
2822 | __d_drop(target); | 2829 | ___d_drop(target); |
2823 | 2830 | ||
2824 | /* Switch the names.. */ | 2831 | /* Switch the names.. */ |
2825 | if (exchange) | 2832 | if (exchange) |
@@ -2831,6 +2838,8 @@ static void __d_move(struct dentry *dentry, struct dentry *target, | |||
2831 | __d_rehash(dentry); | 2838 | __d_rehash(dentry); |
2832 | if (exchange) | 2839 | if (exchange) |
2833 | __d_rehash(target); | 2840 | __d_rehash(target); |
2841 | else | ||
2842 | target->d_hash.pprev = NULL; | ||
2834 | 2843 | ||
2835 | /* ... and switch them in the tree */ | 2844 | /* ... and switch them in the tree */ |
2836 | if (IS_ROOT(dentry)) { | 2845 | if (IS_ROOT(dentry)) { |