vfork(): Fix a race condition in the SMP case. Existing logic depended on the fact that the child would not run until waitpid was called because the child had the same priority as the parent. BUT in the SMP case that is not true... the child may run immediately on a different CPU.

This commit is contained in:
Gregory Nutt 2016-11-19 07:30:01 -06:00
parent 0db99b8c89
commit ceacacbc63

View File

@ -41,6 +41,7 @@
#include <sys/wait.h> #include <sys/wait.h>
#include <stdint.h> #include <stdint.h>
#include <sched.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
@ -396,36 +397,38 @@ pid_t task_vforkstart(FAR struct task_tcb_s *child)
pid = (int)child->cmn.pid; pid = (int)child->cmn.pid;
/* Eliminate a race condition by disabling pre-emption. The child task
* can be instantiated, but cannot run until we call waitpid(). This
* assures us that we cannot miss the the death-of-child signal (only
* needed in the SMP case).
*/
sched_lock();
/* Activate the task */ /* Activate the task */
ret = task_activate((FAR struct tcb_s *)child); ret = task_activate((FAR struct tcb_s *)child);
if (ret < OK) if (ret < OK)
{ {
task_vforkabort(child, -ret); task_vforkabort(child, -ret);
sched_unlock();
return ERROR; return ERROR;
} }
/* Since the child task has the same priority as the parent task, it is /* The child task has not yet ran because pre-emption is disabled.
* now ready to run, but has not yet ran. It is a requirement that * The child task has the same priority as the parent task, so that
* the parent environment be stable while vfork runs; the child thread * would be the case anyway. However, in the SMP case, the child
* is still dependent on things in the parent thread... like the pointers * thread may have already ran on another CPU.
* into parent thread's stack which will still appear in the child's
* registers and environment.
* *
* We do not have SIG_CHILD, so we have to do some silly things here. * It is a requirement that the parent environment be stable while
* The simplest way to make sure that the child thread runs to completion * vfork runs; the child thread is still dependent on things in the
* is simply to yield here. Since the child can only do exit() or * parent thread... like the pointers into parent thread's stack
* execv/l(), that should be all that is needed. * which will still appear in the child's registers and environment.
*
* Hmmm.. this is probably not sufficient. What if we are running
* SCHED_RR? What if the child thread is suspended and rescheduled
* after the parent thread again?
*/ */
/* We can also exploit a bug in the execv() implementation: The PID /* Now wait for the child thread to exit before returning to the
* of the task exec'ed by the child will not be the same as the PID of * parent thread. NOTE that pre-emption will be re-enabled while
* the child task. Therefore, waitpid() on the child task's PID will * we are waiting.
* accomplish what we need to do.
*/ */
rc = 0; rc = 0;
@ -440,6 +443,7 @@ pid_t task_vforkstart(FAR struct task_tcb_s *child)
(void)waitpid(pid, &rc, 0); (void)waitpid(pid, &rc, 0);
#endif #endif
sched_unlock();
return pid; return pid;
} }