]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
unshare: Fix PDEATHSIG race for --kill-child
authorEarl Chew <earl_chew@yahoo.com>
Sat, 1 Jan 2022 22:44:47 +0000 (14:44 -0800)
committerEarl Chew <earl_chew@yahoo.com>
Sun, 9 Jan 2022 22:07:00 +0000 (14:07 -0800)
Kill the child explicitly should the parent terminate
just before the child invokes prctl(PR_SET_PDEATHSIG).

The underlying issue can be reproduced as follows:

  #!/bin/bash

  rm -f /tmp/unshare.log.*
  strace -ff -tt -o /tmp/unshare.log /usr/bin/unshare --kill-child --fork true
  head -1 /tmp/unshare.log.*

  while : ; do
    PARENT=$(
      /usr/bin/unshare --kill-child --fork bash -c 'echo $PPID' &
      sleep 0 # sleep 0.00$RANDOM
      kill -9 $! 2>/dev/null
    )
    [ "$PARENT" != 1 ] || { echo "$PARENT" ; break ; }
  done

Signed-off-by: Earl Chew <earl_chew@yahoo.com>
sys-utils/unshare.c

index 58a5e180c866e2e6718dbca160ddc6be67469389..2dbab9161a40b33f4fa746050590f3cfb06b013a 100644 (file)
@@ -763,7 +763,7 @@ int main(int argc, char *argv[])
        const char *newroot = NULL;
        const char *newdir = NULL;
        pid_t pid_bind = 0, pid_idmap = 0;
-       pid_t pid = 0;
+       pid_t pid = 0, pid_parent = 0;
        int fd_idmap, fd_bind = -1;
        int status;
        unsigned long propagation = UNSHARE_PROPAGATION_DEFAULT;
@@ -952,6 +952,7 @@ int main(int argc, char *argv[])
 
                /* force child forking before mountspace binding
                 * so pid_for_children is populated */
+               pid_parent = getpid();
                pid = fork();
 
                switch(pid) {
@@ -989,8 +990,17 @@ int main(int argc, char *argv[])
                err(EXIT_FAILURE, _("child exit failed"));
        }
 
-       if (kill_child_signo != 0 && prctl(PR_SET_PDEATHSIG, kill_child_signo) < 0)
-               err(EXIT_FAILURE, "prctl failed");
+       if (kill_child_signo != 0) {
+               if (prctl(PR_SET_PDEATHSIG, kill_child_signo) < 0)
+                       err(EXIT_FAILURE, "prctl failed");
+
+               if (getppid() != pid_parent) {
+                       if (kill(getpid(), kill_child_signo) != 0)
+                               err(EXIT_FAILURE, _("child kill failed"));
+                       /* The selected kill_child_signo be blocked, or
+                        * might not cause termination. */
+               }
+       }
 
         if (mapuser != (uid_t) -1 && !usermap)
                map_id(_PATH_PROC_UIDMAP, mapuser, real_euid);