[PATCH net v9 12/14] afs: Fix lack of setting call->server when doing FS.InlineBulkStatus

netdev-bot+sashiko at kernel.org netdev-bot+sashiko at kernel.org
Tue Sep 8 13:49:49 PDT 2026


Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:

Critical: 0 · High: 1 · Medium: 1 · Low: 1

- [High] Use-after-free of struct afs_call via the stale op->call pointer
  in afs_wait_for_operation() (fs/afs/fs_operation.c:281-301).
- [Medium] afs_make_op_call() (fs/afs/internal.h:1758) now makes every
  fileserver-operation call hold an *active* count on op->server.
- [Low] Commit message names a non-existent function as the release site
  of the new server reference.

--

afs: Fix lack of setting call->server when doing FS.InlineBulkStatus

This moves afs_make_op_call() further down fs/afs/internal.h so that it can
call afs_use_server(), and has it set call->server from op->server.  A new
afs_server_trace_use_call trace enum is added for the new active count.

Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260824091645.415423-1-dhowells%40redhat.com

> Note that this requires afs_make_op_call() to be moved so that it can use
> afs_use_server(); the server is un-used by afs_free_server().

[Severity: Low]
Should this say afs_free_call() rather than afs_free_server()?  There is no
afs_free_server() in fs/afs; the server teardown helpers are
afs_server_destroyer(), afs_server_rcu() and __afs_put_server() in
fs/afs/server.c.

The active count taken by the new afs_use_server() in afs_make_op_call()
is dropped on call teardown, in afs_free_call():

fs/afs/rxrpc.c:afs_free_call() {
	...
	afs_unuse_server_notime(call->net, call->server, afs_server_trace_unuse_call);
	kfree(call->request);
	...
}

> diff --git a/fs/afs/internal.h b/fs/afs/internal.h
> index 290873bac89b3..65a0866cd8b85 100644
> --- a/fs/afs/internal.h
> +++ b/fs/afs/internal.h

[ ... ]

> @@ -1763,6 +1747,23 @@ static inline struct inode *AFS_VNODE_TO_I(struct afs_vnode *vnode)
>  	return &vnode->netfs.inode;
>  }
>  
> +static inline void afs_make_op_call(struct afs_operation *op, struct afs_call *call,
> +				    gfp_t gfp)
> +{
> +	struct afs_addr_list *alist = op->estate->addresses;
> +
> +	op->call	= afs_get_call(call, afs_call_trace_get);
> +	op->type	= call->type;
> +	call->op	= op;
> +	call->server	= afs_use_server(op->server, false, afs_server_trace_use_call);

[Severity: Medium]
Can this leave an idle afs_server with no garbage collection armed?

Every fileserver operation call now holds an active count on op->server,
and the only counterpart drop is the _notime variant in afs_free_call():

fs/afs/server.c:afs_unuse_server_notime() {
	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);
	}
	afs_put_server(net, server, reason);
}

Unlike afs_unuse_server(), this never sets server->unuse_time and never
calls afs_set_server_timer().  So if the call's active count happens to be
the last one dropped, the server sits at active == 0 with no
AFS_SERVER_FL_EXPIRED bit, no armed server->timer and no queued destroyer.

In the synchronous path this doesn't arise, because afs_wait_for_operation()
drops the call ref before afs_put_operation() -> afs_put_serverlist(), so
the last active drop goes through the timing-aware afs_unuse_server().

The async read path has the opposite order:

fs/afs/file.c:afs_read_receive() {
	op->call		= NULL;
	call->op		= NULL;
	afs_put_call(call);
	...
	afs_end_read(op);   /* -> afs_put_operation -> afs_put_serverlist */
}

afs_fetch_data_async_rx() then drops the final call ref afterwards, so
afs_free_call()'s afs_unuse_server_notime() is the last active drop.  If the
volume's server list was replaced concurrently (so afs_put_serverlist() drops
the list's last reference), does the afs_server, its endpoint state, address
list and peer bindings then stay in cell->fs_servers until the cell reaches
AFS_CELL_REMOVING or the netns goes away, rather than after
afs_server_gc_delay?

> +	call->key	= op->key;
> +	call->intr	= !(op->flags & AFS_OPERATION_UNINTR);
> +	call->peer	= rxrpc_kernel_get_peer(alist->addrs[op->addr_index].peer);
> +	call->service_id = op->server->service_id;
> +	afs_make_call(call, gfp);
> +	afs_put_call(call);
> +}
> +

[Severity: High]
This assignment of op->call is the only producer for the rotation loop in
afs_wait_for_operation(), and that loop never clears op->call:

fs/afs/fs_operation.c:afs_wait_for_operation() {
	while (afs_select_fileserver(op)) {
		...
		if (op->call) {
			afs_wait_for_call_to_complete(op->call);
			op->call_abort_code = op->call->abort_code;
			op->call_error = op->call->error;
			op->call_responded = op->call->responded;
			afs_put_call(op->call);
		}
	}

For a synchronous call, the reference in op->call is the last one
(afs_alloc_flat_call sets ref to 1, afs_get_call() here makes it 2, and the
trailing afs_put_call() above brings it back to 1), so afs_put_call() ->
afs_free_call() -> kfree(call) while op->call still points at it.

Can a second loop iteration then use freed memory?  If the issue function
returns without reaching afs_make_op_call(), the if (op->call) test is true
for the stale pointer, and afs_wait_for_call_to_complete() reads call->state,
call->net->socket and call->rxcall, followed by a second afs_put_call() on
the same freed call.

With this patch, that second put also drops a server active count that
afs_free_call() already released via afs_unuse_server_notime(), so does it
underflow server->active/server->ref too?

The early returns reachable on a second iteration look like:

fs/afs/fsclient.c:afs_fs_inline_bulk_status() {
	if (test_bit(AFS_SERVER_FL_NO_IBULK, &op->server->flags)) {
		afs_op_set_error(op, -ENOTSUPP);
		return;
	}

That NO_IBULK bit only becomes settable because of this patch, since
afs_done_fs_inline_bulk_status() is its sole setter and previously oopsed on
call->server == NULL:

fs/afs/fsclient.c:afs_done_fs_inline_bulk_status() {
		set_bit(AFS_SERVER_FL_NO_IBULK, &call->server->flags);

Two more paths reach the same state: afs_alloc_flat_call() returning NULL
under GFP_NOFS, and the

		else
			op->call_error = -ENOTSUPP;

branch in afs_wait_for_operation() when the newly selected server has no
matching issue_*_rpc.  Getting a second iteration is ordinary control flow -
afs_select_fileserver() returns true again for VBUSY/VOFFLINE, for
RX_CALL_TIMEOUT and for -ETIMEDOUT/-EHOSTUNREACH.

Would clearing op->call before the put fix this, matching what
afs_read_receive() already does with op->call = NULL?

A git diff of fs/afs between this commit and the tip of the series is empty,
so this doesn't look addressed by a later patch in the series.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260907113743.1453210-1-dhowells%40redhat.com



More information about the linux-afs mailing list