diff options
author | Kiran Kumar Modukuri <kiran.modukuri@gmail.com> | 2018-06-21 16:31:44 -0400 |
---|---|---|
committer | David Howells <dhowells@redhat.com> | 2018-07-25 09:49:00 -0400 |
commit | f29507ce66701084c39aeb1b0ae71690cbff3554 (patch) | |
tree | 32c6a56a27fc9a00b9e5f956a618193210603f00 /fs/fscache | |
parent | 934140ab028713a61de8bca58c05332416d037d1 (diff) |
fscache: Fix reference overput in fscache_attach_object() error handling
When a cookie is allocated that causes fscache_object structs to be
allocated, those objects are initialised with the cookie pointer, but
aren't blessed with a ref on that cookie unless the attachment is
successfully completed in fscache_attach_object().
If attachment fails because the parent object was dying or there was a
collision, fscache_attach_object() returns without incrementing the cookie
counter - but upon failure of this function, the object is released which
then puts the cookie, whether or not a ref was taken on the cookie.
Fix this by taking a ref on the cookie when it is assigned in
fscache_object_init(), even when we're creating a root object.
Analysis from Kiran Kumar:
This bug has been seen in 4.4.0-124-generic #148-Ubuntu kernel
BugLink: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1776277
fscache cookie ref count updated incorrectly during fscache object
allocation resulting in following Oops.
kernel BUG at /build/linux-Y09MKI/linux-4.4.0/fs/fscache/internal.h:321!
kernel BUG at /build/linux-Y09MKI/linux-4.4.0/fs/fscache/cookie.c:639!
[Cause]
Two threads are trying to do operate on a cookie and two objects.
(1) One thread tries to unmount the filesystem and in process goes over a
huge list of objects marking them dead and deleting the objects.
cookie->usage is also decremented in following path:
nfs_fscache_release_super_cookie
-> __fscache_relinquish_cookie
->__fscache_cookie_put
->BUG_ON(atomic_read(&cookie->usage) <= 0);
(2) A second thread tries to lookup an object for reading data in following
path:
fscache_alloc_object
1) cachefiles_alloc_object
-> fscache_object_init
-> assign cookie, but usage not bumped.
2) fscache_attach_object -> fails in cant_attach_object because the
cookie's backing object or cookie's->parent object are going away
3) fscache_put_object
-> cachefiles_put_object
->fscache_object_destroy
->fscache_cookie_put
->BUG_ON(atomic_read(&cookie->usage) <= 0);
[NOTE from dhowells] It's unclear as to the circumstances in which (2) can
take place, given that thread (1) is in nfs_kill_super(), however a
conflicting NFS mount with slightly different parameters that creates a
different superblock would do it. A backtrace from Kiran seems to show
that this is a possibility:
kernel BUG at/build/linux-Y09MKI/linux-4.4.0/fs/fscache/cookie.c:639!
...
RIP: __fscache_cookie_put+0x3a/0x40 [fscache]
Call Trace:
__fscache_relinquish_cookie+0x87/0x120 [fscache]
nfs_fscache_release_super_cookie+0x2d/0xb0 [nfs]
nfs_kill_super+0x29/0x40 [nfs]
deactivate_locked_super+0x48/0x80
deactivate_super+0x5c/0x60
cleanup_mnt+0x3f/0x90
__cleanup_mnt+0x12/0x20
task_work_run+0x86/0xb0
exit_to_usermode_loop+0xc2/0xd0
syscall_return_slowpath+0x4e/0x60
int_ret_from_sys_call+0x25/0x9f
[Fix] Bump up the cookie usage in fscache_object_init, when it is first
being assigned a cookie atomically such that the cookie is added and bumped
up if its refcount is not zero. Remove the assignment in
fscache_attach_object().
[Testcase]
I have run ~100 hours of NFS stress tests and not seen this bug recur.
[Regression Potential]
- Limited to fscache/cachefiles.
Fixes: ccc4fc3d11e9 ("FS-Cache: Implement the cookie management part of the netfs API")
Signed-off-by: Kiran Kumar Modukuri <kiran.modukuri@gmail.com>
Signed-off-by: David Howells <dhowells@redhat.com>
Diffstat (limited to 'fs/fscache')
-rw-r--r-- | fs/fscache/cache.c | 2 | ||||
-rw-r--r-- | fs/fscache/cookie.c | 7 | ||||
-rw-r--r-- | fs/fscache/object.c | 1 |
3 files changed, 6 insertions, 4 deletions
diff --git a/fs/fscache/cache.c b/fs/fscache/cache.c index c184c5a356ff..cdcb376ef8df 100644 --- a/fs/fscache/cache.c +++ b/fs/fscache/cache.c | |||
@@ -220,6 +220,7 @@ int fscache_add_cache(struct fscache_cache *cache, | |||
220 | { | 220 | { |
221 | struct fscache_cache_tag *tag; | 221 | struct fscache_cache_tag *tag; |
222 | 222 | ||
223 | ASSERTCMP(ifsdef->cookie, ==, &fscache_fsdef_index); | ||
223 | BUG_ON(!cache->ops); | 224 | BUG_ON(!cache->ops); |
224 | BUG_ON(!ifsdef); | 225 | BUG_ON(!ifsdef); |
225 | 226 | ||
@@ -248,7 +249,6 @@ int fscache_add_cache(struct fscache_cache *cache, | |||
248 | if (!cache->kobj) | 249 | if (!cache->kobj) |
249 | goto error; | 250 | goto error; |
250 | 251 | ||
251 | ifsdef->cookie = &fscache_fsdef_index; | ||
252 | ifsdef->cache = cache; | 252 | ifsdef->cache = cache; |
253 | cache->fsdef = ifsdef; | 253 | cache->fsdef = ifsdef; |
254 | 254 | ||
diff --git a/fs/fscache/cookie.c b/fs/fscache/cookie.c index 97137d7ec5ee..83bfe04456b6 100644 --- a/fs/fscache/cookie.c +++ b/fs/fscache/cookie.c | |||
@@ -516,6 +516,7 @@ static int fscache_alloc_object(struct fscache_cache *cache, | |||
516 | goto error; | 516 | goto error; |
517 | } | 517 | } |
518 | 518 | ||
519 | ASSERTCMP(object->cookie, ==, cookie); | ||
519 | fscache_stat(&fscache_n_object_alloc); | 520 | fscache_stat(&fscache_n_object_alloc); |
520 | 521 | ||
521 | object->debug_id = atomic_inc_return(&fscache_object_debug_id); | 522 | object->debug_id = atomic_inc_return(&fscache_object_debug_id); |
@@ -571,6 +572,8 @@ static int fscache_attach_object(struct fscache_cookie *cookie, | |||
571 | 572 | ||
572 | _enter("{%s},{OBJ%x}", cookie->def->name, object->debug_id); | 573 | _enter("{%s},{OBJ%x}", cookie->def->name, object->debug_id); |
573 | 574 | ||
575 | ASSERTCMP(object->cookie, ==, cookie); | ||
576 | |||
574 | spin_lock(&cookie->lock); | 577 | spin_lock(&cookie->lock); |
575 | 578 | ||
576 | /* there may be multiple initial creations of this object, but we only | 579 | /* there may be multiple initial creations of this object, but we only |
@@ -610,9 +613,7 @@ static int fscache_attach_object(struct fscache_cookie *cookie, | |||
610 | spin_unlock(&cache->object_list_lock); | 613 | spin_unlock(&cache->object_list_lock); |
611 | } | 614 | } |
612 | 615 | ||
613 | /* attach to the cookie */ | 616 | /* Attach to the cookie. The object already has a ref on it. */ |
614 | object->cookie = cookie; | ||
615 | fscache_cookie_get(cookie, fscache_cookie_get_attach_object); | ||
616 | hlist_add_head(&object->cookie_link, &cookie->backing_objects); | 617 | hlist_add_head(&object->cookie_link, &cookie->backing_objects); |
617 | 618 | ||
618 | fscache_objlist_add(object); | 619 | fscache_objlist_add(object); |
diff --git a/fs/fscache/object.c b/fs/fscache/object.c index 20e0d0a4dc8c..9edc920f651f 100644 --- a/fs/fscache/object.c +++ b/fs/fscache/object.c | |||
@@ -327,6 +327,7 @@ void fscache_object_init(struct fscache_object *object, | |||
327 | object->store_limit_l = 0; | 327 | object->store_limit_l = 0; |
328 | object->cache = cache; | 328 | object->cache = cache; |
329 | object->cookie = cookie; | 329 | object->cookie = cookie; |
330 | fscache_cookie_get(cookie, fscache_cookie_get_attach_object); | ||
330 | object->parent = NULL; | 331 | object->parent = NULL; |
331 | #ifdef CONFIG_FSCACHE_OBJECT_LIST | 332 | #ifdef CONFIG_FSCACHE_OBJECT_LIST |
332 | RB_CLEAR_NODE(&object->objlist_link); | 333 | RB_CLEAR_NODE(&object->objlist_link); |