Looking at "int vforked" in signal handler is racy

Denys Vlasenko <[email protected]> Sat, 9 Aug 2025 15:29:06 +0200
Newsgroups org.kernel.vger.dash
Message-ID <[email protected]>
struct job *vforkexec(union node *n, char **argv, const char *path, int idx)
{
         struct job *jp;
         int pid;

         jp = makejob(1);

         sigblockall(NULL);
         vforked++;

<<<< Parent can get a signal here.

         pid = vfork();

         if (!pid) {
                 forkchild(jp, n, FORK_FG);
                 sigclearmask();
                 shellexec(argv, path, idx);
                 /* NOTREACHED */
         }
<<<< Parent can get a signal here. The window is in fact not that small:
<<<< in the child, execve() syscall is rather complex (needs to tear down memory
<<<< mappings, which causes TLB invalidation) and takes time.
<<<< all this time parent is blocked and does not execute,
<<<< so it don't yet reach the next line:

         vforked = 0;
	sigclearmask();


Signal handler:

void
onsig(int signo)
{
         if (vforked)
                 return;
^^^^^^^^^^ this assumes we dot signal in the vforked child,
but it may be false! We may be in the parent!


How to solve this.... at any given time,
there are only two processes which share address space via vfork,
all other possibly existing copies of shell processes (such as
the grandparent shell if the parent is a subshell) have a
separate address space.

So, in signal handler, we are in three possible situations:

* we don't have a "vfork sibling" at all (we aren't after vfork)
* we are after vfork and we are parent: need to handle the signal
* we are after vfork and we are child: must not mess up the parent's
   address space, thus do NOT handle the signal
   (or else it can e.g. set intpending = 1 *in the parent too*!)

I see this solution:

vfork_parent_pid = getpid();
have_vfork_sibling = 1;
pid = vfork();
if (!pid) { child ops; NORETURN; }
have_vfork_sibling = 0;

void
onsig(int signo)
{
         if (after_vfork && getpid() != vfork_parent_pid)
                 /* We are vfork child, DO NOT MODIFY ANY VARIABLES! */
                 return;