[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
Open
Conversation
…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>
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
marked this pull request as ready for review
August 21, 2026 04:23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
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 bothio_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.
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.
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.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: