Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

cachefiles: fix slab-use-after-free in fscache_withdraw_volume()

We got the following issue in our fault injection stress test:

==================================================================
BUG: KASAN: slab-use-after-free in fscache_withdraw_volume+0x2e1/0x370
Read of size 4 at addr ffff88810680be08 by task ondemand-04-dae/5798

CPU: 0 PID: 5798 Comm: ondemand-04-dae Not tainted 6.8.0-dirty #565
Call Trace:
kasan_check_range+0xf6/0x1b0
fscache_withdraw_volume+0x2e1/0x370
cachefiles_withdraw_volume+0x31/0x50
cachefiles_withdraw_cache+0x3ad/0x900
cachefiles_put_unbind_pincount+0x1f6/0x250
cachefiles_daemon_release+0x13b/0x290
__fput+0x204/0xa00
task_work_run+0x139/0x230

Allocated by task 5820:
__kmalloc+0x1df/0x4b0
fscache_alloc_volume+0x70/0x600
__fscache_acquire_volume+0x1c/0x610
erofs_fscache_register_volume+0x96/0x1a0
erofs_fscache_register_fs+0x49a/0x690
erofs_fc_fill_super+0x6c0/0xcc0
vfs_get_super+0xa9/0x140
vfs_get_tree+0x8e/0x300
do_new_mount+0x28c/0x580
[...]

Freed by task 5820:
kfree+0xf1/0x2c0
fscache_put_volume.part.0+0x5cb/0x9e0
erofs_fscache_unregister_fs+0x157/0x1b0
erofs_kill_sb+0xd9/0x1c0
deactivate_locked_super+0xa3/0x100
vfs_get_super+0x105/0x140
vfs_get_tree+0x8e/0x300
do_new_mount+0x28c/0x580
[...]
==================================================================

Following is the process that triggers the issue:

mount failed | daemon exit
------------------------------------------------------------
deactivate_locked_super cachefiles_daemon_release
erofs_kill_sb
erofs_fscache_unregister_fs
fscache_relinquish_volume
__fscache_relinquish_volume
fscache_put_volume(fscache_volume, fscache_volume_put_relinquish)
zero = __refcount_dec_and_test(&fscache_volume->ref, &ref);
cachefiles_put_unbind_pincount
cachefiles_daemon_unbind
cachefiles_withdraw_cache
cachefiles_withdraw_volumes
list_del_init(&volume->cache_link)
fscache_free_volume(fscache_volume)
cache->ops->free_volume
cachefiles_free_volume
list_del_init(&cachefiles_volume->cache_link);
kfree(fscache_volume)
cachefiles_withdraw_volume
fscache_withdraw_volume
fscache_volume->n_accesses
// fscache_volume UAF !!!

The fscache_volume in cache->volumes must not have been freed yet, but its
reference count may be 0. So use the new fscache_try_get_volume() helper
function try to get its reference count.

If the reference count of fscache_volume is 0, fscache_put_volume() is
freeing it, so wait for it to be removed from cache->volumes.

If its reference count is not 0, call cachefiles_withdraw_volume() with
reference count protection to avoid the above issue.

Fixes: fe2140e2f57f ("cachefiles: Implement volume support")
Signed-off-by: Baokun Li <libaokun1@huawei.com>
Link: https://lore.kernel.org/r/20240628062930.2467993-3-libaokun@huaweicloud.com
Signed-off-by: Christian Brauner <brauner@kernel.org>

authored by

Baokun Li and committed by
Christian Brauner
522018a0 85b08b31

+14
+10
fs/cachefiles/cache.c
··· 8 8 #include <linux/slab.h> 9 9 #include <linux/statfs.h> 10 10 #include <linux/namei.h> 11 + #include <trace/events/fscache.h> 11 12 #include "internal.h" 12 13 13 14 /* ··· 320 319 _enter(""); 321 320 322 321 for (;;) { 322 + struct fscache_volume *vcookie = NULL; 323 323 struct cachefiles_volume *volume = NULL; 324 324 325 325 spin_lock(&cache->object_list_lock); 326 326 if (!list_empty(&cache->volumes)) { 327 327 volume = list_first_entry(&cache->volumes, 328 328 struct cachefiles_volume, cache_link); 329 + vcookie = fscache_try_get_volume(volume->vcookie, 330 + fscache_volume_get_withdraw); 331 + if (!vcookie) { 332 + spin_unlock(&cache->object_list_lock); 333 + cpu_relax(); 334 + continue; 335 + } 329 336 list_del_init(&volume->cache_link); 330 337 } 331 338 spin_unlock(&cache->object_list_lock); ··· 341 332 break; 342 333 343 334 cachefiles_withdraw_volume(volume); 335 + fscache_put_volume(vcookie, fscache_volume_put_withdraw); 344 336 } 345 337 346 338 _leave("");
+4
include/trace/events/fscache.h
··· 35 35 fscache_volume_get_cookie, 36 36 fscache_volume_get_create_work, 37 37 fscache_volume_get_hash_collision, 38 + fscache_volume_get_withdraw, 38 39 fscache_volume_free, 39 40 fscache_volume_new_acquire, 40 41 fscache_volume_put_cookie, 41 42 fscache_volume_put_create_work, 42 43 fscache_volume_put_hash_collision, 43 44 fscache_volume_put_relinquish, 45 + fscache_volume_put_withdraw, 44 46 fscache_volume_see_create_work, 45 47 fscache_volume_see_hash_wake, 46 48 fscache_volume_wait_create_work, ··· 122 120 EM(fscache_volume_get_cookie, "GET cook ") \ 123 121 EM(fscache_volume_get_create_work, "GET creat") \ 124 122 EM(fscache_volume_get_hash_collision, "GET hcoll") \ 123 + EM(fscache_volume_get_withdraw, "GET withd") \ 125 124 EM(fscache_volume_free, "FREE ") \ 126 125 EM(fscache_volume_new_acquire, "NEW acq ") \ 127 126 EM(fscache_volume_put_cookie, "PUT cook ") \ 128 127 EM(fscache_volume_put_create_work, "PUT creat") \ 129 128 EM(fscache_volume_put_hash_collision, "PUT hcoll") \ 130 129 EM(fscache_volume_put_relinquish, "PUT relnq") \ 130 + EM(fscache_volume_put_withdraw, "PUT withd") \ 131 131 EM(fscache_volume_see_create_work, "SEE creat") \ 132 132 EM(fscache_volume_see_hash_wake, "SEE hwake") \ 133 133 E_(fscache_volume_wait_create_work, "WAIT crea")