[PATCH RFC v2] 9p/trans_fd: enforce non-blocking I/O with IOCB_NOWAIT
"syzbot" <[email protected]> Thu, 30 Jul 2026 12:55:34 +0000 (UTC)
| Newsgroups | dev.linux.lists.syzbot |
|---|---|
| Message-ID | <[email protected]> |
The 9p filesystem's file descriptor transport uses workqueues to handle
asynchronous reading and writing. When a connection is established, it sets
the O_NONBLOCK flag on the provided file descriptors to prevent workers
from hanging on I/O. However, because the file descriptors are shared with
userspace, a malicious or buggy userspace program can clear the O_NONBLOCK
flag concurrently using fcntl(fd, F_SETFL, ...).
If userspace clears the O_NONBLOCK flag, the read worker (p9_read_work) can
block indefinitely in kernel_read() when reading from an empty pipe. When
the connection is subsequently destroyed, p9_conn_destroy() calls
cancel_work_sync(&m->rq), which hangs forever waiting for the blocked read
worker, eventually triggering the khungtaskd watchdog:
INFO: task blocked for more than 143 seconds.
Call Trace:
<TASK>
__schedule+0x17e7/0x5630 kernel/sched/core.c:7234
schedule+0x164/0x2b0 kernel/sched/core.c:7326
schedule_timeout+0xc0/0x2c0 kernel/time/sleep_timeout.c:75
wait_for_completion+0x2ca/0x5e0 kernel/sched/completion.c:153
__flush_work+0xa63/0xca0 kernel/workqueue.c:4374
__cancel_work_sync+0xbe/0x110 kernel/workqueue.c:4494
p9_conn_destroy net/9p/trans_fd.c:805 [inline]
p9_fd_close+0x23c/0x3f0 net/9p/trans_fd.c:840
p9_client_create+0x8a6/0xc60 net/9p/client.c:939
v9fs_session_init+0x46/0xd40 fs/9p/v9fs.c:471
v9fs_get_tree+0xbd/0xac0 fs/9p/vfs_super.c:97
vfs_get_tree+0x92/0x2a0 fs/super.c:1694
do_new_mount+0x319/0xdc0 fs/namespace.c:3841
__se_sys_mount+0x31d/0x420 fs/namespace.c:4367
do_syscall_64+0x15f/0x560 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
To fix this, do not rely on file->f_flags & O_NONBLOCK to ensure
non-blocking I/O. Instead, replace kernel_read() and kernel_write() with
vfs_iocb_iter_read() and vfs_iocb_iter_write(), passing a kiocb with the
IOCB_NOWAIT flag set. This guarantees non-blocking behavior regardless of
the f_flags state, completely eliminating the race condition with
userspace.
Fixes: ef575281b21e ("9p/trans_fd: always use O_NONBLOCK read/write")
Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=ed53e35a1e9dde289579
Link: https://syzkaller.appspot.com/ai_job?id=7f77983d-c40c-432f-aac6-3aa4872417cb
To: "Dominique Martinet" <[email protected]>
To: "Eric Van Hensbergen" <[email protected]>
To: "Latchesar Ionkov" <[email protected]>
To: <[email protected]>
Cc: <[email protected]>
Cc: "Christian Schoenebeck" <[email protected]>
---
v2:
- Limit read and write sizes to MAX_RW_COUNT to match kernel_read() and kernel_write() behavior.
- Restore task I/O accounting by calling add_rchar()/add_wchar() and inc_syscr()/inc_syscw().
- Include <linux/sched/xacct.h> for task accounting helper functions.
v1:
https://lore.kernel.org/all/[email protected]/T/
---
diff --git a/net/9p/trans_fd.c b/net/9p/trans_fd.c
index eb685b52a..db524bd30 100644
--- a/net/9p/trans_fd.c
+++ b/net/9p/trans_fd.c
@@ -22,6 +22,8 @@
#include <linux/uaccess.h>
#include <linux/inet.h>
#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/sched/xacct.h>
#include <linux/fs_context.h>
#include <linux/slab.h>
#include <linux/seq_file.h>
@@ -217,7 +219,9 @@ static int p9_fd_read(struct p9_client *client, void *v, int len)
{
int ret;
struct p9_trans_fd *ts = NULL;
- loff_t pos;
+ struct kiocb kiocb;
+ struct iov_iter iter;
+ struct kvec iov = { .iov_base = v, .iov_len = min_t(size_t, len, MAX_RW_COUNT) };
if (client && client->status != Disconnected)
ts = client->trans;
@@ -225,11 +229,19 @@ static int p9_fd_read(struct p9_client *client, void *v, int len)
if (!ts)
return -EREMOTEIO;
- if (!(ts->rd->f_flags & O_NONBLOCK))
- p9_debug(P9_DEBUG_ERROR, "blocking read ...\n");
+ init_sync_kiocb(&kiocb, ts->rd);
+ kiocb.ki_pos = ts->rd->f_pos;
+ kiocb.ki_flags |= IOCB_NOWAIT;
+
+ iov_iter_kvec(&iter, ITER_DEST, &iov, 1, iov.iov_len);
+
+ ret = vfs_iocb_iter_read(ts->rd, &kiocb, &iter);
+ if (ret > 0) {
+ ts->rd->f_pos = kiocb.ki_pos;
+ add_rchar(current, ret);
+ }
+ inc_syscr(current);
- pos = ts->rd->f_pos;
- ret = kernel_read(ts->rd, v, len, &pos);
if (ret <= 0 && ret != -ERESTARTSYS && ret != -EAGAIN)
client->status = Disconnected;
return ret;
@@ -385,6 +397,9 @@ static int p9_fd_write(struct p9_client *client, void *v, int len)
{
ssize_t ret;
struct p9_trans_fd *ts = NULL;
+ struct kiocb kiocb;
+ struct iov_iter iter;
+ struct kvec iov = { .iov_base = v, .iov_len = min_t(size_t, len, MAX_RW_COUNT) };
if (client && client->status != Disconnected)
ts = client->trans;
@@ -392,10 +407,19 @@ static int p9_fd_write(struct p9_client *client, void *v, int len)
if (!ts)
return -EREMOTEIO;
- if (!(ts->wr->f_flags & O_NONBLOCK))
- p9_debug(P9_DEBUG_ERROR, "blocking write ...\n");
+ init_sync_kiocb(&kiocb, ts->wr);
+ kiocb.ki_pos = ts->wr->f_pos;
+ kiocb.ki_flags |= IOCB_NOWAIT;
+
+ iov_iter_kvec(&iter, ITER_SOURCE, &iov, 1, iov.iov_len);
+
+ ret = vfs_iocb_iter_write(ts->wr, &kiocb, &iter);
+ if (ret > 0) {
+ ts->wr->f_pos = kiocb.ki_pos;
+ add_wchar(current, ret);
+ }
+ inc_syscw(current);
- ret = kernel_write(ts->wr, v, len, &ts->wr->f_pos);
if (ret <= 0 && ret != -ERESTARTSYS && ret != -EAGAIN)
client->status = Disconnected;
return ret;
@@ -728,21 +752,11 @@ static int p9_fd_open(struct p9_client *client, int rfd, int wfd)
goto out_free_ts;
if (!(ts->rd->f_mode & FMODE_READ))
goto out_put_rd;
- /* Prevent workers from hanging on IO when fd is a pipe.
- * It's technically possible for userspace or concurrent mounts to
- * modify this flag concurrently, which will likely result in a
- * broken filesystem. However, just having bad flags here should
- * not crash the kernel or cause any other sort of bug, so mark this
- * particular data race as intentional so that tooling (like KCSAN)
- * can allow it and detect further problems.
- */
- data_race(ts->rd->f_flags |= O_NONBLOCK);
ts->wr = fget(wfd);
if (!ts->wr)
goto out_put_rd;
if (!(ts->wr->f_mode & FMODE_WRITE))
goto out_put_wr;
- data_race(ts->wr->f_flags |= O_NONBLOCK);
client->trans = ts;
client->status = Connected;
@@ -784,8 +798,6 @@ static int p9_socket_open(struct p9_client *client, struct socket *csocket)
client->trans = p;
client->status = Connected;
- p->rd->f_flags |= O_NONBLOCK;
-
p9_conn_create(client);
return 0;
}
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
--
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).
See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at [email protected].