[PATCH v2 3/3] net/9p: abort interrupted fd RPCs on fatal signals

From: Ze Tan

Date: Thu Jul 16 2026 - 03:33:43 EST


Syzkaller reported a hung task while a thread group was dumping core. One
thread was blocked in p9_client_rpc() on a 9p fd mount while another
thread entered coredump_wait(). The coredump path sent a fatal signal to
the blocked thread and waited for it to exit, but the 9p client sent TFLUSH
and waited for an unresponsive server to acknowledge it.

INFO: task syz.1.4497:22259 blocked for more than 143 seconds.
Call trace:
__switch_to
__schedule
schedule
schedule_timeout
__wait_for_common
wait_for_completion_state
vfs_coredump
get_signal
do_notify_resume

Commit 6b4f48728faa ("net/9p: fix infinite loop in p9_client_rpc on fatal
signal") stopped retrying an interrupted TFLUSH when a fatal signal is
pending. The original non-flush RPC can still enter the TFLUSH path first,
which needlessly depends on the server while the task is trying to exit.

For transports that explicitly retain sent requests across an
asynchronous abort, cancel unsent requests as usual and mark sent requests
aborted without issuing TFLUSH. The fd receive or teardown path keeps the
request and tag alive until it releases the transport reference.

Do not enable this path for zero-copy RPCs or transports without the
required lifetime guarantee.

Fixes: 91b8534fa8f5 ("9p: make rpc code common and rework flush code")
Signed-off-by: Ze Tan <tanze@xxxxxxxxxx>
---
net/9p/client.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)

diff --git a/net/9p/client.c b/net/9p/client.c
index b9860ccb224b..47353cf0750d 100644
--- a/net/9p/client.c
+++ b/net/9p/client.c
@@ -538,6 +538,22 @@ static struct p9_req_t *p9_client_prepare_req(struct p9_client *c,
return ERR_PTR(err);
}

+static void p9_client_abort(struct p9_client *c, struct p9_req_t *req)
+{
+ /*
+ * A fatal signal cannot wait for TFLUSH, but a sent request must keep
+ * its tag until a late reply arrives or the transport is torn down.
+ */
+ if (!c->trans_mod->supports_async_abort ||
+ READ_ONCE(req->status) >= REQ_STATUS_RCVD)
+ return;
+
+ if (!c->trans_mod->cancel(c, req))
+ return;
+
+ cmpxchg(&req->status, REQ_STATUS_SENT, REQ_STATUS_ABORTED);
+}
+
/**
* p9_client_rpc - issue a request and wait for a response
* @c: client session
@@ -617,6 +633,15 @@ p9_client_rpc(struct p9_client *c, int8_t type, const char *fmt, ...)
goto recalc_sigpending;
}

+ if (fatal_signal_pending(current) &&
+ c->trans_mod->supports_async_abort) {
+ p9_debug(P9_DEBUG_MUX, "fatal signal: skip flush\n");
+ p9_client_abort(c, req);
+ if (READ_ONCE(req->status) == REQ_STATUS_RCVD)
+ err = 0;
+ goto recalc_sigpending;
+ }
+
p9_debug(P9_DEBUG_MUX, "flushing\n");
sigpending = 1;
clear_thread_flag(TIF_SIGPENDING);
--
2.43.0