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