[PATCH v2] afs: Fix server UAF during peer appdata lookup
Jérémy Jean
Jeremy.Jean at oss.cyber.gouv.fr
Sat Nov 7 01:52:29 PST 2026
AF_RXRPC does not take a reference on objects stored in peer appdata, so
afs_find_server() can race with afs_server_destroyer(): it may read an old
server pointer and pass it to afs_use_server() after the object has been
freed through RCU. KASAN reports the UAF as:
BUG: KASAN: slab-use-after-free in afs_use_server+0x2e/0x250
Write of size 4 at addr ffff888002b910f0 by task afs-peer-uaf/87
A lookup that overlaps destruction can also queue destroyer work while
dropping the last reference, or leave an inactive server without a GC
timer.
Keep the peer appdata lookup under RCU and take the callback reference
with __refcount_inc_not_zero(). Make the unuse paths rearm the server
timer instead of queueing destroyer work directly. This lets timer shutdown
serialize teardown and retries destruction after an active callback wins
the race.
Fixes: 40e8b52fe8c8 ("afs: Use the per-peer app data provided by rxrpc")
Fixes: 4882ba78574e ("afs: Fix afs_server ref accounting")
Cc: stable at vger.kernel.org
Signed-off-by: Jérémy Jean <Jeremy.Jean at oss.cyber.gouv.fr>
---
Changes since v1 (after review from Sashiko):
- Use the timer when an unuse path needs to retry destruction.
- Rearm the GC timer after a callback drops the last active use.
v1: https://lore.kernel.org/all/20261103231930.3213821-2-Jeremy.Jean@oss.cyber.gouv.fr/
Sashiko: https://sashiko.dev/#/patchset/20261103231930.3213821-2-Jeremy.Jean%40oss.cyber.gouv.fr
fs/afs/server.c | 28 ++++++++++++++++++++--------
1 file changed, 20 insertions(+), 8 deletions(-)
diff --git a/fs/afs/server.c b/fs/afs/server.c
index 189138bd6d712..499ea4f2fbab9 100644
--- a/fs/afs/server.c
+++ b/fs/afs/server.c
@@ -23,11 +23,21 @@ static void afs_server_destroyer(struct work_struct *work);
*/
struct afs_server *afs_find_server(const struct rxrpc_peer *peer)
{
- struct afs_server *server = (struct afs_server *)rxrpc_kernel_get_peer_data(peer);
+ struct afs_server *server;
+ unsigned int a;
+ int r;
- if (!server)
- return NULL;
- return afs_use_server(server, false, afs_server_trace_use_cm_call);
+ rcu_read_lock();
+ server = (struct afs_server *)rxrpc_kernel_get_peer_data(peer);
+ if (server && __refcount_inc_not_zero(&server->ref, &r)) {
+ a = atomic_inc_return(&server->active);
+ trace_afs_server(server->debug_id, r + 1, a,
+ afs_server_trace_use_cm_call);
+ } else {
+ server = NULL;
+ }
+ rcu_read_unlock();
+ return server;
}
/*
@@ -356,9 +366,11 @@ void afs_unuse_server_notime(struct afs_net *net, struct afs_server *server,
return;
if (atomic_dec_and_test(&server->active)) {
- if (test_bit(AFS_SERVER_FL_EXPIRED, &server->flags) ||
- READ_ONCE(server->cell->state) >= AFS_CELL_REMOVING)
- schedule_work(&server->destroyer);
+ unsigned long expires = jiffies;
+
+ if (READ_ONCE(server->cell->state) < AFS_CELL_REMOVING)
+ expires += afs_server_gc_delay * HZ;
+ timer_reduce(&server->timer, expires);
}
afs_put_server(net, server, reason);
@@ -381,7 +393,7 @@ void afs_unuse_server(struct afs_net *net, struct afs_server *server,
server->unuse_time = unuse_time;
afs_set_server_timer(server, afs_server_gc_delay);
} else {
- schedule_work(&server->destroyer);
+ timer_reduce(&server->timer, jiffies);
}
}
--
2.47.3
More information about the linux-afs
mailing list