public inbox for io-uring@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] io_uring: gate personality per opcode to fix TOCTOU check in io_msg_ring_prep
@ 2026-01-25  7:53 clingfei
  2026-01-25 14:16 ` Jens Axboe
  0 siblings, 1 reply; 5+ messages in thread
From: clingfei @ 2026-01-25  7:53 UTC (permalink / raw)
  To: axboe; +Cc: io-uring, linux-kernel, clf700383

From: Cheng Lingfei <clf700383@gmail.com>

Add allow_personality io_issue_def and reject personality use in
io_init_req for opcodes that do not permit it. This fixes a TOCTOU
window in the prior implementation: userspace could race-update
sqe->personality and bypass the __io_msg_ring_prep personality check.

Signed-off-by: Cheng Lingfei <clf700383@gmail.com>
---
 io_uring/io_uring.c |  2 ++
 io_uring/msg_ring.c |  2 +-
 io_uring/opdef.c    | 64 +++++++++++++++++++++++++++++++++++++++++++++
 io_uring/opdef.h    |  2 ++
 4 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c
index b7a077c11c21..7a898b19a340 100644
--- a/io_uring/io_uring.c
+++ b/io_uring/io_uring.c
@@ -2219,6 +2219,8 @@ static int io_init_req(struct io_ring_ctx *ctx, struct io_kiocb *req,
 	personality = READ_ONCE(sqe->personality);
 	if (personality) {
 		int ret;
+		if (unlikely(!def->allow_personality))
+			return -EINVAL;
 
 		req->creds = xa_load(&ctx->personalities, personality);
 		if (!req->creds)
diff --git a/io_uring/msg_ring.c b/io_uring/msg_ring.c
index 7063ea7964e7..758635753da6 100644
--- a/io_uring/msg_ring.c
+++ b/io_uring/msg_ring.c
@@ -260,7 +260,7 @@ static int io_msg_send_fd(struct io_kiocb *req, unsigned int issue_flags)
 
 static int __io_msg_ring_prep(struct io_msg *msg, const struct io_uring_sqe *sqe)
 {
-	if (unlikely(sqe->buf_index || sqe->personality))
+	if (unlikely(sqe->buf_index))
 		return -EINVAL;
 
 	msg->src_file = NULL;
diff --git a/io_uring/opdef.c b/io_uring/opdef.c
index df52d760240e..ee2dfc673fdf 100644
--- a/io_uring/opdef.c
+++ b/io_uring/opdef.c
@@ -55,6 +55,7 @@ const struct io_issue_def io_issue_defs[] = {
 	[IORING_OP_NOP] = {
 		.audit_skip		= 1,
 		.iopoll			= 1,
+		.allow_personality = 1,
 		.prep			= io_nop_prep,
 		.issue			= io_nop,
 	},
@@ -69,6 +70,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.iopoll			= 1,
 		.iopoll_queue		= 1,
 		.vectored		= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_async_rw),
 		.prep			= io_prep_readv,
 		.issue			= io_read,
@@ -84,6 +86,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.iopoll			= 1,
 		.iopoll_queue		= 1,
 		.vectored		= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_async_rw),
 		.prep			= io_prep_writev,
 		.issue			= io_write,
@@ -91,6 +94,7 @@ const struct io_issue_def io_issue_defs[] = {
 	[IORING_OP_FSYNC] = {
 		.needs_file		= 1,
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.prep			= io_fsync_prep,
 		.issue			= io_fsync,
 	},
@@ -103,6 +107,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.ioprio			= 1,
 		.iopoll			= 1,
 		.iopoll_queue		= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_async_rw),
 		.prep			= io_prep_read_fixed,
 		.issue			= io_read_fixed,
@@ -117,6 +122,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.ioprio			= 1,
 		.iopoll			= 1,
 		.iopoll_queue		= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_async_rw),
 		.prep			= io_prep_write_fixed,
 		.issue			= io_write_fixed,
@@ -125,17 +131,20 @@ const struct io_issue_def io_issue_defs[] = {
 		.needs_file		= 1,
 		.unbound_nonreg_file	= 1,
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.prep			= io_poll_add_prep,
 		.issue			= io_poll_add,
 	},
 	[IORING_OP_POLL_REMOVE] = {
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.prep			= io_poll_remove_prep,
 		.issue			= io_poll_remove,
 	},
 	[IORING_OP_SYNC_FILE_RANGE] = {
 		.needs_file		= 1,
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.prep			= io_sfr_prep,
 		.issue			= io_sync_file_range,
 	},
@@ -144,6 +153,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.unbound_nonreg_file	= 1,
 		.pollout		= 1,
 		.ioprio			= 1,
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.async_size		= sizeof(struct io_async_msghdr),
 		.prep			= io_sendmsg_prep,
@@ -158,6 +168,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.pollin			= 1,
 		.buffer_select		= 1,
 		.ioprio			= 1,
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.async_size		= sizeof(struct io_async_msghdr),
 		.prep			= io_recvmsg_prep,
@@ -168,6 +179,7 @@ const struct io_issue_def io_issue_defs[] = {
 	},
 	[IORING_OP_TIMEOUT] = {
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_timeout_data),
 		.prep			= io_timeout_prep,
 		.issue			= io_timeout,
@@ -175,6 +187,7 @@ const struct io_issue_def io_issue_defs[] = {
 	[IORING_OP_TIMEOUT_REMOVE] = {
 		/* used by timeout updates' prep() */
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.prep			= io_timeout_remove_prep,
 		.issue			= io_timeout_remove,
 	},
@@ -184,6 +197,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.pollin			= 1,
 		.poll_exclusive		= 1,
 		.ioprio			= 1,	/* used for flags */
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.prep			= io_accept_prep,
 		.issue			= io_accept,
@@ -193,11 +207,13 @@ const struct io_issue_def io_issue_defs[] = {
 	},
 	[IORING_OP_ASYNC_CANCEL] = {
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.prep			= io_async_cancel_prep,
 		.issue			= io_async_cancel,
 	},
 	[IORING_OP_LINK_TIMEOUT] = {
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_timeout_data),
 		.prep			= io_link_timeout_prep,
 		.issue			= io_no_issue,
@@ -206,6 +222,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.needs_file		= 1,
 		.unbound_nonreg_file	= 1,
 		.pollout		= 1,
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.async_size		= sizeof(struct io_async_msghdr),
 		.prep			= io_connect_prep,
@@ -217,25 +234,30 @@ const struct io_issue_def io_issue_defs[] = {
 	[IORING_OP_FALLOCATE] = {
 		.needs_file		= 1,
 		.hash_reg_file          = 1,
+		.allow_personality = 1,
 		.prep			= io_fallocate_prep,
 		.issue			= io_fallocate,
 	},
 	[IORING_OP_OPENAT] = {
+		.allow_personality = 1,
 		.prep			= io_openat_prep,
 		.issue			= io_openat,
 	},
 	[IORING_OP_CLOSE] = {
+		.allow_personality = 1,
 		.prep			= io_close_prep,
 		.issue			= io_close,
 	},
 	[IORING_OP_FILES_UPDATE] = {
 		.audit_skip		= 1,
 		.iopoll			= 1,
+		.allow_personality = 1,
 		.prep			= io_files_update_prep,
 		.issue			= io_files_update,
 	},
 	[IORING_OP_STATX] = {
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.prep			= io_statx_prep,
 		.issue			= io_statx,
 	},
@@ -249,6 +271,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.ioprio			= 1,
 		.iopoll			= 1,
 		.iopoll_queue		= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_async_rw),
 		.prep			= io_prep_read,
 		.issue			= io_read,
@@ -263,6 +286,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.ioprio			= 1,
 		.iopoll			= 1,
 		.iopoll_queue		= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_async_rw),
 		.prep			= io_prep_write,
 		.issue			= io_write,
@@ -270,11 +294,13 @@ const struct io_issue_def io_issue_defs[] = {
 	[IORING_OP_FADVISE] = {
 		.needs_file		= 1,
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.prep			= io_fadvise_prep,
 		.issue			= io_fadvise,
 	},
 	[IORING_OP_MADVISE] = {
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.prep			= io_madvise_prep,
 		.issue			= io_madvise,
 	},
@@ -285,6 +311,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.audit_skip		= 1,
 		.ioprio			= 1,
 		.buffer_select		= 1,
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.async_size		= sizeof(struct io_async_msghdr),
 		.prep			= io_sendmsg_prep,
@@ -300,6 +327,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.buffer_select		= 1,
 		.audit_skip		= 1,
 		.ioprio			= 1,
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.async_size		= sizeof(struct io_async_msghdr),
 		.prep			= io_recvmsg_prep,
@@ -309,12 +337,14 @@ const struct io_issue_def io_issue_defs[] = {
 #endif
 	},
 	[IORING_OP_OPENAT2] = {
+		.allow_personality = 1,
 		.prep			= io_openat2_prep,
 		.issue			= io_openat2,
 	},
 	[IORING_OP_EPOLL_CTL] = {
 		.unbound_nonreg_file	= 1,
 		.audit_skip		= 1,
+		.allow_personality = 1,
 #if defined(CONFIG_EPOLL)
 		.prep			= io_epoll_ctl_prep,
 		.issue			= io_epoll_ctl,
@@ -327,18 +357,21 @@ const struct io_issue_def io_issue_defs[] = {
 		.hash_reg_file		= 1,
 		.unbound_nonreg_file	= 1,
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.prep			= io_splice_prep,
 		.issue			= io_splice,
 	},
 	[IORING_OP_PROVIDE_BUFFERS] = {
 		.audit_skip		= 1,
 		.iopoll			= 1,
+		.allow_personality = 1,
 		.prep			= io_provide_buffers_prep,
 		.issue			= io_manage_buffers_legacy,
 	},
 	[IORING_OP_REMOVE_BUFFERS] = {
 		.audit_skip		= 1,
 		.iopoll			= 1,
+		.allow_personality = 1,
 		.prep			= io_remove_buffers_prep,
 		.issue			= io_manage_buffers_legacy,
 	},
@@ -347,11 +380,13 @@ const struct io_issue_def io_issue_defs[] = {
 		.hash_reg_file		= 1,
 		.unbound_nonreg_file	= 1,
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.prep			= io_tee_prep,
 		.issue			= io_tee,
 	},
 	[IORING_OP_SHUTDOWN] = {
 		.needs_file		= 1,
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.prep			= io_shutdown_prep,
 		.issue			= io_shutdown,
@@ -360,22 +395,27 @@ const struct io_issue_def io_issue_defs[] = {
 #endif
 	},
 	[IORING_OP_RENAMEAT] = {
+		.allow_personality = 1,
 		.prep			= io_renameat_prep,
 		.issue			= io_renameat,
 	},
 	[IORING_OP_UNLINKAT] = {
+		.allow_personality = 1,
 		.prep			= io_unlinkat_prep,
 		.issue			= io_unlinkat,
 	},
 	[IORING_OP_MKDIRAT] = {
+		.allow_personality = 1,
 		.prep			= io_mkdirat_prep,
 		.issue			= io_mkdirat,
 	},
 	[IORING_OP_SYMLINKAT] = {
+		.allow_personality = 1,
 		.prep			= io_symlinkat_prep,
 		.issue			= io_symlinkat,
 	},
 	[IORING_OP_LINKAT] = {
+		.allow_personality = 1,
 		.prep			= io_linkat_prep,
 		.issue			= io_linkat,
 	},
@@ -387,24 +427,29 @@ const struct io_issue_def io_issue_defs[] = {
 	},
 	[IORING_OP_FSETXATTR] = {
 		.needs_file = 1,
+		.allow_personality = 1,
 		.prep			= io_fsetxattr_prep,
 		.issue			= io_fsetxattr,
 	},
 	[IORING_OP_SETXATTR] = {
+		.allow_personality = 1,
 		.prep			= io_setxattr_prep,
 		.issue			= io_setxattr,
 	},
 	[IORING_OP_FGETXATTR] = {
 		.needs_file = 1,
+		.allow_personality = 1,
 		.prep			= io_fgetxattr_prep,
 		.issue			= io_fgetxattr,
 	},
 	[IORING_OP_GETXATTR] = {
+		.allow_personality = 1,
 		.prep			= io_getxattr_prep,
 		.issue			= io_getxattr,
 	},
 	[IORING_OP_SOCKET] = {
 		.audit_skip		= 1,
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.prep			= io_socket_prep,
 		.issue			= io_socket,
@@ -418,6 +463,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.plug			= 1,
 		.iopoll			= 1,
 		.iopoll_queue		= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_async_cmd),
 		.prep			= io_uring_cmd_prep,
 		.issue			= io_uring_cmd,
@@ -428,6 +474,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.pollout		= 1,
 		.audit_skip		= 1,
 		.ioprio			= 1,
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.async_size		= sizeof(struct io_async_msghdr),
 		.prep			= io_send_zc_prep,
@@ -441,6 +488,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.unbound_nonreg_file	= 1,
 		.pollout		= 1,
 		.ioprio			= 1,
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.async_size		= sizeof(struct io_async_msghdr),
 		.prep			= io_send_zc_prep,
@@ -455,16 +503,19 @@ const struct io_issue_def io_issue_defs[] = {
 		.pollin			= 1,
 		.buffer_select		= 1,
 		.audit_skip		= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_async_rw),
 		.prep			= io_read_mshot_prep,
 		.issue			= io_read_mshot,
 	},
 	[IORING_OP_WAITID] = {
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_waitid_async),
 		.prep			= io_waitid_prep,
 		.issue			= io_waitid,
 	},
 	[IORING_OP_FUTEX_WAIT] = {
+		.allow_personality = 1,
 #if defined(CONFIG_FUTEX)
 		.prep			= io_futex_prep,
 		.issue			= io_futex_wait,
@@ -473,6 +524,7 @@ const struct io_issue_def io_issue_defs[] = {
 #endif
 	},
 	[IORING_OP_FUTEX_WAKE] = {
+		.allow_personality = 1,
 #if defined(CONFIG_FUTEX)
 		.prep			= io_futex_prep,
 		.issue			= io_futex_wake,
@@ -481,6 +533,7 @@ const struct io_issue_def io_issue_defs[] = {
 #endif
 	},
 	[IORING_OP_FUTEX_WAITV] = {
+		.allow_personality = 1,
 #if defined(CONFIG_FUTEX)
 		.prep			= io_futexv_prep,
 		.issue			= io_futexv_wait,
@@ -490,16 +543,19 @@ const struct io_issue_def io_issue_defs[] = {
 	},
 	[IORING_OP_FIXED_FD_INSTALL] = {
 		.needs_file		= 1,
+		.allow_personality = 1,
 		.prep			= io_install_fixed_fd_prep,
 		.issue			= io_install_fixed_fd,
 	},
 	[IORING_OP_FTRUNCATE] = {
 		.needs_file		= 1,
 		.hash_reg_file		= 1,
+		.allow_personality = 1,
 		.prep			= io_ftruncate_prep,
 		.issue			= io_ftruncate,
 	},
 	[IORING_OP_BIND] = {
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.needs_file		= 1,
 		.prep			= io_bind_prep,
@@ -510,6 +566,7 @@ const struct io_issue_def io_issue_defs[] = {
 #endif
 	},
 	[IORING_OP_LISTEN] = {
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.needs_file		= 1,
 		.prep			= io_listen_prep,
@@ -524,6 +581,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.unbound_nonreg_file	= 1,
 		.pollin			= 1,
 		.ioprio			= 1,
+		.allow_personality = 1,
 #if defined(CONFIG_NET)
 		.prep			= io_recvzc_prep,
 		.issue			= io_recvzc,
@@ -535,6 +593,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.needs_file		= 1,
 		.audit_skip		= 1,
 		.pollin			= 1,
+		.allow_personality = 1,
 #if defined(CONFIG_EPOLL)
 		.prep			= io_epoll_wait_prep,
 		.issue			= io_epoll_wait,
@@ -552,6 +611,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.iopoll			= 1,
 		.iopoll_queue		= 1,
 		.vectored		= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_async_rw),
 		.prep			= io_prep_readv_fixed,
 		.issue			= io_read,
@@ -567,11 +627,13 @@ const struct io_issue_def io_issue_defs[] = {
 		.iopoll			= 1,
 		.iopoll_queue		= 1,
 		.vectored		= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_async_rw),
 		.prep			= io_prep_writev_fixed,
 		.issue			= io_write,
 	},
 	[IORING_OP_PIPE] = {
+		.allow_personality = 1,
 		.prep			= io_pipe_prep,
 		.issue			= io_pipe,
 	},
@@ -579,6 +641,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.audit_skip		= 1,
 		.iopoll			= 1,
 		.is_128			= 1,
+		.allow_personality = 1,
 		.prep			= io_nop_prep,
 		.issue			= io_nop,
 	},
@@ -589,6 +652,7 @@ const struct io_issue_def io_issue_defs[] = {
 		.iopoll			= 1,
 		.iopoll_queue		= 1,
 		.is_128			= 1,
+		.allow_personality = 1,
 		.async_size		= sizeof(struct io_async_cmd),
 		.prep			= io_uring_cmd_prep,
 		.issue			= io_uring_cmd,
diff --git a/io_uring/opdef.h b/io_uring/opdef.h
index aa37846880ff..f4a98339aa79 100644
--- a/io_uring/opdef.h
+++ b/io_uring/opdef.h
@@ -29,6 +29,8 @@ struct io_issue_def {
 	unsigned		vectored : 1;
 	/* set to 1 if this opcode uses 128b sqes in a mixed sq */
 	unsigned		is_128 : 1;
+	/* set to 1 if this opcode allows using personality */
+	unsigned    allow_personality : 1;
 
 	/* size of async data needed, if any */
 	unsigned short		async_size;
-- 
2.34.1


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

* Re: [PATCH] io_uring: gate personality per opcode to fix TOCTOU check in io_msg_ring_prep
  2026-01-25  7:53 [PATCH] io_uring: gate personality per opcode to fix TOCTOU check in io_msg_ring_prep clingfei
@ 2026-01-25 14:16 ` Jens Axboe
  2026-01-26  1:57   ` clingfei
  0 siblings, 1 reply; 5+ messages in thread
From: Jens Axboe @ 2026-01-25 14:16 UTC (permalink / raw)
  To: clingfei; +Cc: io-uring, linux-kernel

On 1/25/26 12:53 AM, clingfei wrote:
> From: Cheng Lingfei <clf700383@gmail.com>
> 
> Add allow_personality io_issue_def and reject personality use in
> io_init_req for opcodes that do not permit it. This fixes a TOCTOU
> window in the prior implementation: userspace could race-update
> sqe->personality and bypass the __io_msg_ring_prep personality check.

Please do detail what the bug is here, this looks like some kind of
AI hallucination. The check in msg_ring prep exists just to reject
commands with it set, for future expansion. The only thing that
matters is the ordering and use in io_init_req(), which is fine.

-- 
Jens Axboe


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

* Re: [PATCH] io_uring: gate personality per opcode to fix TOCTOU check in io_msg_ring_prep
  2026-01-25 14:16 ` Jens Axboe
@ 2026-01-26  1:57   ` clingfei
  2026-01-26 12:36     ` Jens Axboe
  0 siblings, 1 reply; 5+ messages in thread
From: clingfei @ 2026-01-26  1:57 UTC (permalink / raw)
  To: Jens Axboe; +Cc: io-uring, linux-kernel

Jens Axboe <axboe@kernel.dk> 于2026年1月25日周日 22:16写道:
>
> On 1/25/26 12:53 AM, clingfei wrote:
> > From: Cheng Lingfei <clf700383@gmail.com>
> >
> > Add allow_personality io_issue_def and reject personality use in
> > io_init_req for opcodes that do not permit it. This fixes a TOCTOU
> > window in the prior implementation: userspace could race-update
> > sqe->personality and bypass the __io_msg_ring_prep personality check.
>
> Please do detail what the bug is here, this looks like some kind of
> AI hallucination. The check in msg_ring prep exists just to reject
> commands with it set, for future expansion. The only thing that
> matters is the ordering and use in io_init_req(), which is fine.
>
> --
> Jens Axboe
>
Sorry, I forgot to reply to all in the previous email.

The io_init_req checks sqe->personality; if personality is not zero,
req->creds is initialized based on personality. The msg_ring prep also checks
sqe->personality and rejects non-zero personality values. However, sqe is
shared between the kernel and userspace. This means a user can submit a
msg_ring SQE with a non-zero personality. After passing the check in
io_init_req, the user process can concurrently modify personality to
set it to 0,
thus enabling it to pass the check in msg_ring prep and invalidating
its rejection
of non-zero `personality` values.

This is not an AI hallucination, and it can be demonstrated by a userspace PoC.

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

* Re: [PATCH] io_uring: gate personality per opcode to fix TOCTOU check in io_msg_ring_prep
  2026-01-26  1:57   ` clingfei
@ 2026-01-26 12:36     ` Jens Axboe
  2026-01-27  2:24       ` clingfei
  0 siblings, 1 reply; 5+ messages in thread
From: Jens Axboe @ 2026-01-26 12:36 UTC (permalink / raw)
  To: clingfei; +Cc: io-uring, linux-kernel

On 1/25/26 6:57 PM, clingfei wrote:
> Jens Axboe <axboe@kernel.dk> ?2026?1?25??? 22:16???
>>
>> On 1/25/26 12:53 AM, clingfei wrote:
>>> From: Cheng Lingfei <clf700383@gmail.com>
>>>
>>> Add allow_personality io_issue_def and reject personality use in
>>> io_init_req for opcodes that do not permit it. This fixes a TOCTOU
>>> window in the prior implementation: userspace could race-update
>>> sqe->personality and bypass the __io_msg_ring_prep personality check.
>>
>> Please do detail what the bug is here, this looks like some kind of
>> AI hallucination. The check in msg_ring prep exists just to reject
>> commands with it set, for future expansion. The only thing that
>> matters is the ordering and use in io_init_req(), which is fine.
>>
>> --
>> Jens Axboe
>>
> Sorry, I forgot to reply to all in the previous email.
> 
> The io_init_req checks sqe->personality; if personality is not zero,
> req->creds is initialized based on personality. The msg_ring prep also checks
> sqe->personality and rejects non-zero personality values. However, sqe is
> shared between the kernel and userspace. This means a user can submit a
> msg_ring SQE with a non-zero personality. After passing the check in
> io_init_req, the user process can concurrently modify personality to
> set it to 0,
> thus enabling it to pass the check in msg_ring prep and invalidating
> its rejection
> of non-zero `personality` values.

I'm not disputing you can't change ->personality between io_init_req()
and __io_msg_ring_prep(), that's obviously possible. I'm saying that it
doesn't matter one bit at all, as the check in __io_msg_ring_prep() is
just there for future proofing. If the application knowingly changes the
SQE post submit to defeat an -EINVAL return, then yes it'll defeat the
future proofing. But so what? If you look at other prep handlers, the
-EINVAL checks never made any attempts at protecting against this
scenario, as it's only there for future proofing.

IOW, you could just remove this check in __io_msg_ring_prep() and it'd
be fine. Or just leave it alone, because it really doesn't serve a
purpose.

> This is not an AI hallucination, and it can be demonstrated by a
> userspace PoC.

It does read like one, however... Including this reply.

-- 
Jens Axboe

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

* Re: [PATCH] io_uring: gate personality per opcode to fix TOCTOU check in io_msg_ring_prep
  2026-01-26 12:36     ` Jens Axboe
@ 2026-01-27  2:24       ` clingfei
  0 siblings, 0 replies; 5+ messages in thread
From: clingfei @ 2026-01-27  2:24 UTC (permalink / raw)
  To: Jens Axboe; +Cc: io-uring, linux-kernel

Jens Axboe <axboe@kernel.dk> 于2026年1月26日周一 20:36写道:
>
> On 1/25/26 6:57 PM, clingfei wrote:
> > Jens Axboe <axboe@kernel.dk> ?2026?1?25??? 22:16???
> >>
> >> On 1/25/26 12:53 AM, clingfei wrote:
> >>> From: Cheng Lingfei <clf700383@gmail.com>
> >>>
> >>> Add allow_personality io_issue_def and reject personality use in
> >>> io_init_req for opcodes that do not permit it. This fixes a TOCTOU
> >>> window in the prior implementation: userspace could race-update
> >>> sqe->personality and bypass the __io_msg_ring_prep personality check.
> >>
> >> Please do detail what the bug is here, this looks like some kind of
> >> AI hallucination. The check in msg_ring prep exists just to reject
> >> commands with it set, for future expansion. The only thing that
> >> matters is the ordering and use in io_init_req(), which is fine.
> >>
> >> --
> >> Jens Axboe
> >>
> > Sorry, I forgot to reply to all in the previous email.
> >
> > The io_init_req checks sqe->personality; if personality is not zero,
> > req->creds is initialized based on personality. The msg_ring prep also checks
> > sqe->personality and rejects non-zero personality values. However, sqe is
> > shared between the kernel and userspace. This means a user can submit a
> > msg_ring SQE with a non-zero personality. After passing the check in
> > io_init_req, the user process can concurrently modify personality to
> > set it to 0,
> > thus enabling it to pass the check in msg_ring prep and invalidating
> > its rejection
> > of non-zero `personality` values.
>
> I'm not disputing you can't change ->personality between io_init_req()
> and __io_msg_ring_prep(), that's obviously possible. I'm saying that it
> doesn't matter one bit at all, as the check in __io_msg_ring_prep() is
> just there for future proofing. If the application knowingly changes the
> SQE post submit to defeat an -EINVAL return, then yes it'll defeat the
> future proofing. But so what? If you look at other prep handlers, the
> -EINVAL checks never made any attempts at protecting against this
> scenario, as it's only there for future proofing.
>
> IOW, you could just remove this check in __io_msg_ring_prep() and it'd
> be fine. Or just leave it alone, because it really doesn't serve a
> purpose.
>
Thanks for your patient reply.

I agree that this does not matter since it cannot be leveraged for
further exploitation.
Initially, I read the Linux 6.6 version of the code, which can only
affect fpl->user
in __io_scm_file_account when the cred is substituted with another process's.
However, I found that this portion has been removed in the latest
kernel version...

By the way, I did read other prep handlers’ code, but almost none included
a similar double-check that could be bypassed in the same manner, which is
why I find this a bit strange.

> > This is not an AI hallucination, and it can be demonstrated by a
> > userspace PoC.
>
> It does read like one, however... Including this reply.
>
> --
> Jens Axboe

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

end of thread, other threads:[~2026-01-27  2:24 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-25  7:53 [PATCH] io_uring: gate personality per opcode to fix TOCTOU check in io_msg_ring_prep clingfei
2026-01-25 14:16 ` Jens Axboe
2026-01-26  1:57   ` clingfei
2026-01-26 12:36     ` Jens Axboe
2026-01-27  2:24       ` clingfei

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