-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Process: correct a subtle runLoopSource
lifetime issue
#3125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@swift-ci please test |
Converting to draft - I think that I hit the failure even with the check? |
Okay, caught the thing under a debugger with the change - it reduces the frequency, but is more indicative of a data race - we now hit it as a UaF in
We can see in
Clearly |
When a process is run with `run`, a monitoring thread is setup by means of a socketpair. This socket is used to create a runloop source, which will monitor the process for termination. `waitUntilExit` will monitor the `isRunning` ivar which is set by the source to indicate the process termination having been observed. We however would previously mark the process as terminated before invalidating the runloop source to prevent any new callouts on the source. However, the operation is multithreaded the operation is not guaranteed to be serialized and the runloop source may be `nil`'ed prior or during the invalidation which also releases as the refcount is now 0. This would result in the `runloopSource` passed to `CFRunLoopSourceInvalidate` being `nil` and breaking the contract of the call or a UaF if the value is `nil`'ed during the execution. These states have both been observed in practice. Reordering the `isRunning` ivar assignment to occur after the invalidation ensures that the value remains usable until we no longer reference it.
runLoopSource
lifetime issue
@swift-ci please test |
The failures seem unrelated - docc timeouts |
@swift-ci please test |
Alternative, which should fix both code paths: |
I think that the re-ordering is actually correct and desirable (though retaining is also something we should do in addition to the re-ordering). The re-ordering ensures that we do not touch the monitor runloop source after we indicate the process termination. We can simply hoist that above the flagging. Refactoring to share the path seems reasonable as well, though, I'd love to see a more invasive refactoring to share most of the path for the monitor. |
@swift-ci please test |
@swift-ci please test |
This fixes a data race between setting `process.isRunning = false` and then calling `CFRunLoopSourceInvalidate(process.runLoopSource)`. Process.waitUntilExit() may wake up and clear .runLoopSource which results in a fairly common crash on Windows. Put this logic in a common method to reduce drift between the Darwin / Linux and Windows code paths. Co-authored-by: Saleem Abdulrasool <[email protected]>
@swift-ci please test |
1 similar comment
@swift-ci please test |
@swift-ci please test Linux platform |
LGTM. Merging if green. Please create a PR against release/5.6 as well? |
#3128 for 5.6 |
Please test with following PRs: @swift-ci please test Linux platform |
When a process is run with
run
, a monitoring thread is setup by meansof a socketpair. This socket is used to create a runloop source, which
will monitor the process for termination.
waitUntilExit
will monitorthe
isRunning
ivar which is set by the source to indicate the processtermination having been observed. We however would previously mark the
process as terminated before invalidating the runloop source to prevent
any new callouts on the source. However, the operation is multithreaded
the operation is not guaranteed to be serialized and the runloop source
may be
nil
'ed prior or during the invalidation which also releases asthe refcount is now 0. This would result in the
runloopSource
passedto
CFRunLoopSourceInvalidate
beingnil
and breaking the contract ofthe call or a UaF if the value is
nil
'ed during the execution. Thesestates have both been observed in practice. Reordering the
isRunning
ivar assignment to occur after the invalidation ensures that the value
remains usable until we no longer reference it.