Skip to content

[Backport][0.9 to 0.8] | | fix(iouring): plug UAF on _async_io early returns (#1569) (#1619) - #1623

Open
photonlibos wants to merge 3 commits into
release/0.8from
backport-pr-dd236e97866fd3d4cbff64b7ada7b69ef9464695-0.8
Open

[Backport][0.9 to 0.8] | | fix(iouring): plug UAF on _async_io early returns (#1569) (#1619)#1623
photonlibos wants to merge 3 commits into
release/0.8from
backport-pr-dd236e97866fd3d4cbff64b7ada7b69ef9464695-0.8

Conversation

@photonlibos

Copy link
Copy Markdown
Collaborator

[Backport][main to 0.9] | fix(iouring): plug UAF on _async_io early returns (#1569) (#1619)

The stack-allocated ioCtx/timer_ctx could go out of scope while
their CQEs were still in flight, on three early-return paths:
timer/cancel SQE allocation failure and the cancel branch itself.
Introduce an ioCtx::done flag set by reap_events on the final CQE;
neutralize the queued first SQE with a NOP, and wait on the done
flags before returning. A generation counter unblocks the waiters
once reset() (fork) drops the old ring, whose CQEs never arrive.
Add regression test test-iouring-uaf. Fixes #1270.

  • chore: drop files not present in 0.9

  • fix(iouring): resolve backport conflicts for the _async_io UAF fix

This engine still has the pre-refactoring shape of main, so the fix had
to be re-expressed rather than applied.

  • io/iouring-wrapper.cpp: set ctx->done in the CQE loop of
    wait_and_fire_events(), which here is separate from wait_for_events()
    and has no is_event handling, so main's unified reap_events() block is
    not imported. The flag is set right after ctx->res, guarded by
    !IORING_CQE_F_MORE, instead of at the end of the loop body: the
    -ECANCELED branches of this branch come first and continue, and both
    io_ctx and timer_ctx report -ECANCELED once a cancellation takes
    effect, so the end of the body is unreachable for exactly the contexts
    the new wait loops in _async_io block on. Verified by experiment: with
    the flag at the end, iouring_uaf.interrupt_storm hangs.
  • io/iouring-wrapper.cpp: drop the try_submit() call the cherry-pick
    brought into the cancel path. That helper does not exist here and
    cannot be ported, as it is gated on m_args.eager_submit, which this
    branch has no equivalent of; submission stays lazy, driven by
    wait_and_fire_events(), as it was before. Note that git reported no
    conflict for this hunk, so it would have been a build break.
  • io/test/CMakeLists.txt: keep the signalfdboom target of this branch and
    register test-iouring-uaf with add_executable/target_link_libraries/
    add_test under the existing if (NOT APPLE), since photon_add_test()
    and if (LINUX) come from main's test-helper refactoring.
  • io/test/test-iouring-uaf.cpp: restore the regression test, which the
    backport tooling dropped as a new file while still referencing it from
    CMakeLists.txt. It needs no adaptation.

Validated: test-iouring-uaf passes all 4 cases, including the interrupt
storms that drive the cancel path over ~1200 rounds; test-iouring (10)
and test-fork (5) pass, the latter covering ForkTest.Iouring, i.e. the
reset()/generation path that releases the waiters.


Co-authored-by: Jiangtian Feng fengjiangtian.fjt@alibaba-inc.com
Co-authored-by: lihuiba 282919+lihuiba@users.noreply.github.com
Co-authored-by: Coldwings coldwings@me.com
Generated by Backport Auto PR, by cherry-pick related commits.

Please review and decide whether to merge or close this backport PR.

Conflicts

Cherry-pick produced conflicts. Conflict markers are committed as-is; please resolve them manually before merging.

Dropped new files

The following new files introduced by cherry-pick were dropped because they don't exist in release/0.8:

  • io/test/test-iouring-uaf.cpp

photonlibos and others added 2 commits August 21, 2026 01:58
…eturns (#1569) (#1619)

* fix(iouring): plug UAF on _async_io early returns (#1569)

The stack-allocated ioCtx/timer_ctx could go out of scope while
their CQEs were still in flight, on three early-return paths:
timer/cancel SQE allocation failure and the cancel branch itself.
Introduce an ioCtx::done flag set by reap_events on the final CQE;
neutralize the queued first SQE with a NOP, and wait on the done
flags before returning. A generation counter unblocks the waiters
once reset() (fork) drops the old ring, whose CQEs never arrive.
Add regression test test-iouring-uaf. Fixes #1270.

* chore: drop files not present in 0.9

* fix(iouring): resolve backport conflicts for the _async_io UAF fix

This engine still has the pre-refactoring shape of main, so the fix had
to be re-expressed rather than applied.

- io/iouring-wrapper.cpp: set ctx->done in the CQE loop of
  wait_and_fire_events(), which here is separate from wait_for_events()
  and has no is_event handling, so main's unified reap_events() block is
  not imported. The flag is set right after ctx->res, guarded by
  !IORING_CQE_F_MORE, instead of at the end of the loop body: the
  -ECANCELED branches of this branch come first and `continue`, and both
  io_ctx and timer_ctx report -ECANCELED once a cancellation takes
  effect, so the end of the body is unreachable for exactly the contexts
  the new wait loops in _async_io block on. Verified by experiment: with
  the flag at the end, iouring_uaf.interrupt_storm hangs.
- io/iouring-wrapper.cpp: drop the try_submit() call the cherry-pick
  brought into the cancel path. That helper does not exist here and
  cannot be ported, as it is gated on m_args.eager_submit, which this
  branch has no equivalent of; submission stays lazy, driven by
  wait_and_fire_events(), as it was before. Note that git reported no
  conflict for this hunk, so it would have been a build break.
- io/test/CMakeLists.txt: keep the signalfdboom target of this branch and
  register test-iouring-uaf with add_executable/target_link_libraries/
  add_test under the existing `if (NOT APPLE)`, since photon_add_test()
  and `if (LINUX)` come from main's test-helper refactoring.
- io/test/test-iouring-uaf.cpp: restore the regression test, which the
  backport tooling dropped as a new file while still referencing it from
  CMakeLists.txt. It needs no adaptation.

Validated: test-iouring-uaf passes all 4 cases, including the interrupt
storms that drive the cancel path over ~1200 rounds; test-iouring (10)
and test-fork (5) pass, the latter covering ForkTest.Iouring, i.e. the
reset()/generation path that releases the waiters.

---------

Co-authored-by: Jiangtian Feng <fengjiangtian.fjt@alibaba-inc.com>
Co-authored-by: lihuiba <282919+lihuiba@users.noreply.github.com>
Co-authored-by: Coldwings <coldwings@me.com>
@photonlibos photonlibos added bugfix A PR that should be back-ported to prior release branches (release/*) needs-manual-merge labels Aug 21, 2026
Unlike the 0.9 backport, the engine here is structurally identical to the
source branch, so the fix applies as-is and little adaptation was needed.

- io/iouring-wrapper.cpp: the only conflict was whitespace around the
  numeric_limits cast, plus the has_timer variable the incoming side
  introduces. Take the incoming side, since the wait loops of the cancel
  path need has_timer.
- io/test/test-iouring-uaf.cpp: restore the regression test, which the
  backport tooling again dropped as a new file. Identical to the 0.9
  version, no adaptation needed.

io/test/CMakeLists.txt needed no resolution: the 0.9 side already
registers the test with add_executable/target_link_libraries/add_test,
which is the style of this branch too.

Checked the parts that applied without conflict, since a clean hunk is
not necessarily a compiling one: the CQE loop of wait_and_fire_events()
has the same shape as 0.9, with the -ECANCELED branches ahead of the
F_MORE check, so the placement of ctx->done before them is still the
correct one here. try_submit() is absent, as the 0.9 backport had already
dropped it.

Validated: test-iouring-uaf (4), test-iouring (10) and test-fork (5) all
pass, the last one covering ForkTest.Iouring, i.e. the reset()/generation
path that releases the waiters.
@Coldwings
Coldwings marked this pull request as ready for review August 21, 2026 04:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix A PR that should be back-ported to prior release branches (release/*)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants