Archive-only list for syzbot
 help / color / mirror / Atom feed
* [PATCH RFC] 9p/trans_fd: enforce non-blocking I/O with IOCB_NOWAIT
@ 2026-06-29 18:08 syzbot
  2026-07-30 11:45 ` Slawomir Stepien
  0 siblings, 1 reply; 2+ messages in thread
From: syzbot @ 2026-06-29 18:08 UTC (permalink / raw)
  To: syzkaller-upstream-moderation; +Cc: syzbot

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: syzbot+ed53e35a1e9dde289579@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=ed53e35a1e9dde289579
Link: https://syzkaller.appspot.com/ai_job?id=372eb0ab-345a-4053-96b2-027ab6aa8d21
To: "Dominique Martinet" <asmadeus@codewreck.org>
To: "Eric Van Hensbergen" <ericvh@kernel.org>
To: "Latchesar Ionkov" <lucho@ionkov.net>
To: <v9fs@lists.linux.dev>
Cc: <linux-kernel@vger.kernel.org>
Cc: "Christian Schoenebeck" <linux_oss@crudebyte.com>

---
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 syzkaller@googlegroups.com.

^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH RFC] 9p/trans_fd: enforce non-blocking I/O with IOCB_NOWAIT
  2026-06-29 18:08 [PATCH RFC] 9p/trans_fd: enforce non-blocking I/O with IOCB_NOWAIT syzbot
@ 2026-07-30 11:45 ` Slawomir Stepien
  0 siblings, 0 replies; 2+ messages in thread
From: Slawomir Stepien @ 2026-07-30 11:45 UTC (permalink / raw)
  To: syzbot; +Cc: syzkaller-upstream-moderation, syzbot

On cze 29, 2026 18:08, syzbot wrote:
> 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: syzbot+ed53e35a1e9dde289579@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=ed53e35a1e9dde289579
> Link: https://syzkaller.appspot.com/ai_job?id=372eb0ab-345a-4053-96b2-027ab6aa8d21
> To: "Dominique Martinet" <asmadeus@codewreck.org>
> To: "Eric Van Hensbergen" <ericvh@kernel.org>
> To: "Latchesar Ionkov" <lucho@ionkov.net>
> To: <v9fs@lists.linux.dev>
> Cc: <linux-kernel@vger.kernel.org>
> Cc: "Christian Schoenebeck" <linux_oss@crudebyte.com>
> 
> ---
> 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 };

I see that __kernel_read() (called from kernel_read()) clamps the iov_len:

	.iov_len	= min_t(size_t, count, MAX_RW_COUNT)

Maybe it is worth doing the same in this patch?

>  	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 the kernel_read() is replaced with vfs_iocb_iter_read() then accounting functions will not be
called like it is with __kernel_read():

	ret = file->f_op->read_iter(&kiocb, &iter);
	if (ret > 0) {
		...
		add_rchar(current, ret);
	}
	inc_syscr(current);

Maybe it would be better to duplicated what kernel_read()/__kernel_read() does but with IOCB_NOWAIT
set, even if that brings a bit of code duplication?

I've done some tests (num of IOPS, mean latency for READ and WRITE) with fio to benchmark this fix
and I see 5% to 10% degradation in performance with this patch.

> +	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 };

ditto

>  	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;

ditto

>  
> -	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

-- 
Slawomir Stepien

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-07-30 11:45 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-29 18:08 [PATCH RFC] 9p/trans_fd: enforce non-blocking I/O with IOCB_NOWAIT syzbot
2026-07-30 11:45 ` Slawomir Stepien

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox