* [BUG] fs/aio: io_setup integer wrap can exhaust global aio quota
@ 2026-10-22 9:17 Cheng Yongkang
2026-10-22 15:23 ` Benjamin LaHaise
0 siblings, 1 reply; 5+ messages in thread
From: Cheng Yongkang @ 2026-10-22 9:17 UTC (permalink / raw)
To: Benjamin LaHaise, Alexander Viro, Christian Brauner
Cc: Jan Kara, linux-aio, linux-fsdevel, linux-kernel
Hi all,
ioctx_alloc() saves the 32-bit nr_events in max_reqs, then doubles
nr_events before checking the ring-size bound. For
nr_events=0x80000001, the doubled value wraps to 2, so the ring check
passes and aio_setup_ring() builds a one-page/127-slot ring. The
original 0x80000001 is still stored in ctx->max_reqs and charged to the
global aio_nr quota.
If an administrator has raised fs.aio-max-nr to at least 0x80000001,
an unprivileged process can create that context and make later normal
io_setup() calls fail with -EAGAIN until the wrapped context is
destroyed.
Observed on Linux v7.3, commit
08ab31164c55f20c7a2653de3186ae333470e2c1, with the administrator
raising fs.aio-max-nr to 2147483649 and dropping to uid/gid 65534:
io_setup(0x80000001): ret=0
fs.aio-nr: 2147483649
ring header nr: 127
ring VMA size: 4096
io_setup(1): -EAGAIN while wrapped context alive
io_destroy(): 0
fs.aio-nr after: 0
The unprivileged io_submit path stopped at the ring's real 127-slot
capacity.
The default fs.aio-max-nr=65536 blocks the path, so this requires the
documented but unusually large administrator configuration. Under that
configuration it is a system-wide availability failure.
Check the magnitude before doubling, or use a 64-bit intermediate and
charge the actual validated request count.
I found no matching upstream fix or CVE.
The privileged init wrapper and unprivileged PoC are available on
request.
This was found with a multi-agent workflow; the full
quota/ring sequence was reproduced in QEMU.
Thanks,
Cheng Yongkang
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [BUG] fs/aio: io_setup integer wrap can exhaust global aio quota
2026-10-22 9:17 [BUG] fs/aio: io_setup integer wrap can exhaust global aio quota Cheng Yongkang
@ 2026-10-22 15:23 ` Benjamin LaHaise
2026-10-22 15:44 ` Cheng Yongkang
0 siblings, 1 reply; 5+ messages in thread
From: Benjamin LaHaise @ 2026-10-22 15:23 UTC (permalink / raw)
To: Cheng Yongkang
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-aio,
linux-fsdevel, linux-kernel
Good morning Cheng,
This is a case of user error. Root can change all sorts of settings in
the system that will break things. Raising aio-max-nr to an impossibly
high value is a stupid thing to do, so it's not surprising that some
syscalls will result in error returns in corner when root exercises its
powers to do something unreasonable.
There is no security issue as the embryonic ioctx is destroyed before it
can be used ioctx_add_table() never gets called, and it is destroyed
before ioctx_alloc() returns. Yes, it Would Be Nice (tm) to avoid the
extra work incurred setting up an ioctx that immediately gets torn down,
but that isn't a bug, rather it is an inefficiency.
No further action is required.
Thank you for the wasted time this morning with your useless AI slop.
-ben
On Thu, Oct 22, 2026 at 02:17:48AM -0700, Cheng Yongkang wrote:
> Hi all,
>
> ioctx_alloc() saves the 32-bit nr_events in max_reqs, then doubles
> nr_events before checking the ring-size bound. For
> nr_events=0x80000001, the doubled value wraps to 2, so the ring check
> passes and aio_setup_ring() builds a one-page/127-slot ring. The
> original 0x80000001 is still stored in ctx->max_reqs and charged to the
> global aio_nr quota.
>
> If an administrator has raised fs.aio-max-nr to at least 0x80000001,
> an unprivileged process can create that context and make later normal
> io_setup() calls fail with -EAGAIN until the wrapped context is
> destroyed.
>
> Observed on Linux v7.3, commit
> 08ab31164c55f20c7a2653de3186ae333470e2c1, with the administrator
> raising fs.aio-max-nr to 2147483649 and dropping to uid/gid 65534:
>
> io_setup(0x80000001): ret=0
> fs.aio-nr: 2147483649
> ring header nr: 127
> ring VMA size: 4096
> io_setup(1): -EAGAIN while wrapped context alive
> io_destroy(): 0
> fs.aio-nr after: 0
>
> The unprivileged io_submit path stopped at the ring's real 127-slot
> capacity.
>
> The default fs.aio-max-nr=65536 blocks the path, so this requires the
> documented but unusually large administrator configuration. Under that
> configuration it is a system-wide availability failure.
>
> Check the magnitude before doubling, or use a 64-bit intermediate and
> charge the actual validated request count.
>
> I found no matching upstream fix or CVE.
>
> The privileged init wrapper and unprivileged PoC are available on
> request.
>
> This was found with a multi-agent workflow; the full
> quota/ring sequence was reproduced in QEMU.
>
> Thanks,
> Cheng Yongkang
>
--
"Thought is the essence of where you are now."
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [BUG] fs/aio: io_setup integer wrap can exhaust global aio quota
2026-10-22 15:23 ` Benjamin LaHaise
@ 2026-10-22 15:44 ` Cheng Yongkang
2026-10-22 15:48 ` Benjamin LaHaise
0 siblings, 1 reply; 5+ messages in thread
From: Cheng Yongkang @ 2026-10-22 15:44 UTC (permalink / raw)
To: Benjamin LaHaise
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-aio,
linux-fsdevel, linux-kernel
Hi Ben,
Thanks for the reply. I agree with the security triage: reaching this path
requires root to raise fs.aio-max-nr to an unreasonable value, and I am not
claiming a memory-safety issue, privilege-boundary bypass, or UAF. The impact
is limited to global AIO quota accounting under that configuration.
I do think there is a factual mismatch regarding the execution path described
in your reply. The validation raises fs.aio-max-nr to 2147483649 before
dropping privileges and calling io_setup(0x80000001). In that configuration:
1. max_reqs retains 0x80000001.
2. nr_events *= 2 wraps the request to 2.
3. The wrapped value passes the ring-size check.
4. aio_setup_ring() creates a one-page, 127-slot ring.
5. aio_nr += ctx->max_reqs charges the original 0x80000001.
6. ioctx_add_table() is called and succeeds.
7. A subsequent normal io_setup(1) fails with -EAGAIN.
So, in this tested path, the ioctx is not destroyed before ioctx_alloc()
returns, and ioctx_add_table() is reached. The ring is small while the global
quota charge is large.
I agree that this is a low-value, configuration-dependent correctness issue,
not a security fix. My point is only that treating the configuration as
unsupported is different from saying the wrapped path is cleaned up before it
can be installed.
I will be more conservative about AI-assisted findings going forward and will
set a higher bar for preconditions, security impact, and whether a patch is
worth maintainer time. If you consider this configuration unsupported, I will
withdraw the patch.
Thanks,
Cheng
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [BUG] fs/aio: io_setup integer wrap can exhaust global aio quota
2026-10-22 15:44 ` Cheng Yongkang
@ 2026-10-22 15:48 ` Benjamin LaHaise
2026-10-22 16:01 ` Cheng Yongkang
0 siblings, 1 reply; 5+ messages in thread
From: Benjamin LaHaise @ 2026-10-22 15:48 UTC (permalink / raw)
To: Cheng Yongkang
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-aio,
linux-fsdevel, linux-kernel
aio_nr is never updated. Your analysis is entirely incorrect.
-ben
On Thu, Oct 22, 2026 at 08:44:06AM -0700, Cheng Yongkang wrote:
> Hi Ben,
>
> Thanks for the reply. I agree with the security triage: reaching this path
> requires root to raise fs.aio-max-nr to an unreasonable value, and I am not
> claiming a memory-safety issue, privilege-boundary bypass, or UAF. The impact
> is limited to global AIO quota accounting under that configuration.
>
> I do think there is a factual mismatch regarding the execution path described
> in your reply. The validation raises fs.aio-max-nr to 2147483649 before
> dropping privileges and calling io_setup(0x80000001). In that configuration:
>
> 1. max_reqs retains 0x80000001.
> 2. nr_events *= 2 wraps the request to 2.
> 3. The wrapped value passes the ring-size check.
> 4. aio_setup_ring() creates a one-page, 127-slot ring.
> 5. aio_nr += ctx->max_reqs charges the original 0x80000001.
> 6. ioctx_add_table() is called and succeeds.
> 7. A subsequent normal io_setup(1) fails with -EAGAIN.
>
> So, in this tested path, the ioctx is not destroyed before ioctx_alloc()
> returns, and ioctx_add_table() is reached. The ring is small while the global
> quota charge is large.
>
> I agree that this is a low-value, configuration-dependent correctness issue,
> not a security fix. My point is only that treating the configuration as
> unsupported is different from saying the wrapped path is cleaned up before it
> can be installed.
>
> I will be more conservative about AI-assisted findings going forward and will
> set a higher bar for preconditions, security impact, and whether a patch is
> worth maintainer time. If you consider this configuration unsupported, I will
> withdraw the patch.
>
> Thanks,
> Cheng
>
--
"Thought is the essence of where you are now."
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [BUG] fs/aio: io_setup integer wrap can exhaust global aio quota
2026-10-22 15:48 ` Benjamin LaHaise
@ 2026-10-22 16:01 ` Cheng Yongkang
0 siblings, 0 replies; 5+ messages in thread
From: Cheng Yongkang @ 2026-10-22 16:01 UTC (permalink / raw)
To: Benjamin LaHaise
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-aio,
linux-fsdevel, linux-kernel
Hi Ben,
Here is the exact path, step by step, with the values at each line of
v7.3 fs/aio.c (commit 08ab31164c55). Precondition: root has set
fs.aio-max-nr to 2147483649 (0x80000001); fs.aio-nr is 0.
The caller does io_setup(0x80000001, &ctx).
1. Line 796: max_reqs = nr_events;
max_reqs = 0x80000001
2. Line 807: nr_events = max(nr_events, num_possible_cpus() * 4);
nr_events = 0x80000001
3. Line 808: nr_events *= 2;
0x80000001 * 2 = 0x100000002, which does not fit in
32 bits, so nr_events = 2
4. Line 811: if (nr_events > 0x10000000U / sizeof(struct io_event))
2 > 8388608 is false, so no return
5. Line 816: if (!nr_events || max_reqs > aio_max_nr)
nr_events is 2, and 0x80000001 > 0x80000001 is false,
so no return
6. Line 845: aio_setup_ring(ctx, nr_events) with nr_events = 2
succeeds and builds a one-page ring (127 slots)
7. Line 856: if (aio_nr + ctx->max_reqs > aio_max_nr || ...)
0 + 0x80000001 > 0x80000001 is false, so no error
8. Line 862: aio_nr += ctx->max_reqs;
aio_nr = 0x80000001 <-- aio_nr is updated here
9. Line 868: err = ioctx_add_table(ctx, mm);
returns 0 <-- ioctx_add_table() is called
10. Line 877: return ctx;
io_setup() returns 0 and the context is live.
You can check it in under a minute:
# sysctl -w fs.aio-max-nr=2147483649
# cat /proc/sys/fs/aio-nr
0
... run io_setup(0x80000001, &ctx) ...
# cat /proc/sys/fs/aio-nr
2147483649
If aio_nr were never updated, the second read would be 0. It is
2147483649. The context returned in step 10 also works with
io_submit() and io_getevents(), which is only possible if
ioctx_add_table() installed it.
The early return you describe is real, but it is step 5 with the
default fs.aio-max-nr of 65536. That is not the configuration in
the report.
I am not asking you to take a patch. I only want the record to be
accurate: on this path, aio_nr is updated and ioctx_add_table() is
called.
Thanks,
Cheng
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-22 16:01 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-22 9:17 [BUG] fs/aio: io_setup integer wrap can exhaust global aio quota Cheng Yongkang
2026-10-22 15:23 ` Benjamin LaHaise
2026-10-22 15:44 ` Cheng Yongkang
2026-10-22 15:48 ` Benjamin LaHaise
2026-10-22 16:01 ` Cheng Yongkang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®