-
Notifications
You must be signed in to change notification settings - Fork 11
Description
After the parent finishes TraceExecutor::onPostForkParent, every time the child receives a signal, it stops, waiting for for a PTRACE_CONT. This not a problem when PerfListener isn't enabled, as when the parent begins its waitid loop in Executor, it will receive the ptrace notification, and the child will be resumed by TraceExecutor::onExecuteEvent.
However, if PerfListener is enabled, it executes after TraceExecutor. It contains a pthread barrier, which requires the presence of both the parent and the child. If the child receives a signal before either of them reach the barrier, the program will be deadlocked, with the child never receiving the PTRACE_CONT, and the parent never moving past the barrier.
Keep in mind this problem is probably mostly theoretical, as it would require:
- A non-critical signal to be delivered to the child at all in that timespan, which is almost impossible with normal execution (however can happen, for example with
SIGWINCH) - The signal needs to hit an extremely precise point in time between
TraceExecutorandPerfListener
With that said, I've managed to reproduce it by adding a sleep(1) at the end of TraceExecutor::onPostForkParent and using that time to resize the window and cause a SIGWINCH to be sent to the child, deadlocking the process. I believe this issue can probably be fixed by moving TraceExecutor after PerfListener in the listener order, however I don't know if that won't break anything else.
A similar problem might also theoretically exist in the TraceExecutor, this time not requiring PerfListener. TraceExecutor::onPostForkParent does only one waitpid call, assuming it will receive the exact SIGTRAP sent by the child in TraceExecutor::onPostForkChild. This might theoretically not be correct if it receives a SIGTSTP before the SIGTRAP or finishes with EINTR, but that seems even more unlikely than the previous scenario to me.