[PATCH RFC] 9p/trans_fd: enforce non-blocking I/O with IOCB_NOWAIT

"syzbot" <[email protected]>
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.1-pro-preview best-expensive syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=ed53e35a1e9dde289579
Link: https://syzkaller.appspot.com/ai_job?id=372eb0ab-345a-4053-96b2-027ab6aa8d21
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]>

---
diff --git a/net/9p/trans_fd.c b/net/9p/trans_fd.c
index eb685b52a..2b3a07b98 100644
--- a/net/9p/trans_fd.c
+++ b/net/9p/trans_fd.c
@@ -22,6 +22,7 @@
 #include <linux/uaccess.h>
 #include <linux/inet.h>
 #include <linux/file.h>
+#include <linux/fs.h>
 #include <linux/fs_context.h>
 #include <linux/slab.h>
 #include <linux/seq_file.h>
@@ -217,7 +218,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 = len };
 
 	if (client && client->status != Disconnected)
 		ts = client->trans;
@@ -225,11 +228,16 @@ 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, len);
+
+	ret = vfs_iocb_iter_read(ts->rd, &kiocb, &iter);
+	if (ret > 0)
+		ts->rd->f_pos = kiocb.ki_pos;
 
-	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 +393,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 = len };
 
 	if (client && client->status != Disconnected)
 		ts = client->trans;
@@ -392,10 +403,16 @@ 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, len);
+
+	ret = vfs_iocb_iter_write(ts->wr, &kiocb, &iter);
+	if (ret > 0)
+		ts->wr->f_pos = kiocb.ki_pos;
 
-	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 +745,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 +791,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].
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.