[BZ,#18433] Check file access/existence before forking.

Message ID CAOUBrm3me5L+9XxRz5-a+vGURKzD4Z9DxJWfx2GpSjL6x8ywhw@mail.gmail.com
State Superseded
Headers

Commit Message

Navid Rahimi Sept. 11, 2015, 7:46 p.m. UTC
  For second patch should I create another request for review or sending
it here is not problematic (since in new patch I am using pipes to
communicate between parent and child it is kinda v2 ) ?

best wishes,
-navid


On Fri, Sep 11, 2015 at 10:52 PM, Mike Frysinger <vapier@gentoo.org> wrote:
> doing an access/stat/whatever doesn't solve the issue you described --
> as others have pointed out, there's a TOCTOU race, but there's also no
> guarantee that doing a stat/access on mode bits means you can actually
> execute that file and/or that the exec will succeed.  since this doesn't
> actually fix the problem you've described (and others have pointed out
> that it's not a spec violation), then this patch as-is is dead.
> -mike
  

Comments

Joseph Myers Sept. 11, 2015, 7:58 p.m. UTC | #1
On Sat, 12 Sep 2015, navid Rahimi wrote:

> +  /* Open Read/Write pipe for parent/child communication */
> +  if (__pipe2 (pipefd, O_CLOEXEC))
> +    return errno;

You can't assume that pipe2 is supported unless __ASSUME_PIPE2 is defined.  
It might be an ENOSYS stub, or it might be unsupported at runtime because 
the kernel isn't recent enough.  In either case, an ENOSYS error from 
pipe2 must not result in such an error from posix_spawn.
  
Navid Rahimi Sept. 11, 2015, 8:33 p.m. UTC | #2
On Sat, Sep 12, 2015 at 12:28 AM, Joseph Myers <joseph@codesourcery.com> wrote:
> On Sat, 12 Sep 2015, navid Rahimi wrote:
> You can't assume that pipe2 is supported unless __ASSUME_PIPE2 is defined.
> It might be an ENOSYS stub, or it might be unsupported at runtime because
> the kernel isn't recent enough.  In either case, an ENOSYS error from
> pipe2 must not result in such an error from posix_spawn.
> Joseph S. Myers
> joseph@codesourcery.com

Freebsd uses vfork (and its blocking nature for parent process) for
implementation of posix_spawn , but I have found cert article about
not using vfork

https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=1703954

pipes was more reliable in overall sight (in my opinion, because I
have seen so much people complaining about vfork) , but I can
implement another patch which fixes this bug with using vfork.
But I don't know should I use vfork ?
  
Roland McGrath Sept. 11, 2015, 8:41 p.m. UTC | #3
That article talks about the complications of using vfork with standard
library interfaces.  That is not directly relevant to using vfork in the
implementation of the library.  The vfork semantics raise subtle issues and
great care needs to be taken when using it, but the article you cited is
not really relevant to implementation choices here.
  
Joseph Myers Sept. 11, 2015, 9:01 p.m. UTC | #4
On Sat, 12 Sep 2015, navid Rahimi wrote:

> On Sat, Sep 12, 2015 at 12:28 AM, Joseph Myers <joseph@codesourcery.com> wrote:
> > On Sat, 12 Sep 2015, navid Rahimi wrote:
> > You can't assume that pipe2 is supported unless __ASSUME_PIPE2 is defined.
> > It might be an ENOSYS stub, or it might be unsupported at runtime because
> > the kernel isn't recent enough.  In either case, an ENOSYS error from
> > pipe2 must not result in such an error from posix_spawn.
> > Joseph S. Myers
> > joseph@codesourcery.com
> 
> Freebsd uses vfork (and its blocking nature for parent process) for
> implementation of posix_spawn , but I have found cert article about
> not using vfork
> 
> https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=1703954
> 
> pipes was more reliable in overall sight (in my opinion, because I
> have seen so much people complaining about vfork) , but I can

Well, you can use pipes provided you have a fallback from pipe2 to pipe in 
the case where __ASSUME_PIPE2 is not defined and pipe2 fails with ENOSYS 
at runtime.  (There would be a race if another thread forks and execs 
between the calls to pipe and fcntl FD_CLOEXEC, but that's unavoidable in 
the absence of pipe2.)
  
Navid Rahimi Sept. 11, 2015, 9:16 p.m. UTC | #5
In this case I think implementing with vfork would be more readable
and clearer than this tricky hack with pipes and this unavoidable race
condition in fallback mode is something we can avoid with vfork.

If you guys can give any advise I would be more than happy to hear. I
will sent patch for this bug (vfork version ).


I have hard time understanding this part of __spawni function :

  /* Generate the new process.  */
  if ((flags & POSIX_SPAWN_USEVFORK) != 0
      /* If no major work is done, allow using vfork.  Note that we
might perform the path searching.  But this would be done by
a call to execvp(), too, and such a call must be OK according
to POSIX.  */
      || ((flags & (POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF
   | POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER
   | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_RESETIDS)) == 0
 && file_actions == NULL))
    new_pid = __vfork ();
  else
    new_pid = __fork ();

Why it is trying to limit usage of vfork ?

best wishes,
-navid


On Sat, Sep 12, 2015 at 1:31 AM, Joseph Myers <joseph@codesourcery.com> wrote:
> Well, you can use pipes provided you have a fallback from pipe2 to pipe in
> the case where __ASSUME_PIPE2 is not defined and pipe2 fails with ENOSYS
> at runtime.  (There would be a race if another thread forks and execs
> between the calls to pipe and fcntl FD_CLOEXEC, but that's unavoidable in
> the absence of pipe2.)
>
> --
> Joseph S. Myers
> joseph@codesourcery.com
  
Roland McGrath Sept. 11, 2015, 9:18 p.m. UTC | #6
> Why it is trying to limit usage of vfork ?

I think that's just being conservative.  For each modification that can be
applied in the child, you need to examine it carefully and figure out
whether it is in fact (or can be made to be) compatible with vfork.
  
Mike Frysinger Sept. 12, 2015, 3:17 a.m. UTC | #7
On 12 Sep 2015 00:16, navid Rahimi wrote:
> +  /* Open Read/Write pipe for parent/child communication */

GNU style says comments are full sentences (so ends in a period) and
there should be two spaces after that.
  /* Open read/write pipe for parent/child communication.  */
-mike
  

Patch

diff --git a/ChangeLog b/ChangeLog
index c9023fb..7cc4b61 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@ 
+2015-09-11  Navid Rahimi  <rahimi.nv@gmail.com>
+
+	[BZ #18433]
+	* sysdeps/posix/spawni.c (__spawni): Check child status.
+
 2015-09-10  Joseph Myers  <joseph@codesourcery.com>
 
 	[BZ #2542]
diff --git a/sysdeps/posix/spawni.c b/sysdeps/posix/spawni.c
index eee9331..e0c5ac4 100644
--- a/sysdeps/posix/spawni.c
+++ b/sysdeps/posix/spawni.c
@@ -89,6 +89,12 @@  __spawni (pid_t *pid, const char *file,
   char *path, *p, *name;
   size_t len;
   size_t pathlen;
+  int pipefd[2];
+
+  errno = 0;
+  /* Open Read/Write pipe for parent/child communication */
+  if (__pipe2 (pipefd, O_CLOEXEC))
+    return errno;
 
   /* Do this once.  */
   short int flags = attrp == NULL ? 0 : attrp->__flags;
@@ -109,20 +115,26 @@  __spawni (pid_t *pid, const char *file,
 
   if (new_pid != 0)
     {
-      if (new_pid < 0)
+      __close (pipefd[1]);
+      if (new_pid < 0){
+	__close (pipefd[0]);
 	return errno;
-
+      }
       /* The call was successful.  Store the PID if necessary.  */
       if (pid != NULL)
 	*pid = new_pid;
 
-      return 0;
+      __read (pipefd[0], &errno, sizeof errno);
+      __close (pipefd[0]);
+      return errno;
     }
+  else
+    __close (pipefd[0]);
 
   /* Set signal mask.  */
   if ((flags & POSIX_SPAWN_SETSIGMASK) != 0
       && __sigprocmask (SIG_SETMASK, &attrp->__ss, NULL) != 0)
-    _exit (SPAWN_ERROR);
+    goto fail;
 
   /* Set signal default action.  */
   if ((flags & POSIX_SPAWN_SETSIGDEF) != 0)
@@ -140,7 +152,7 @@  __spawni (pid_t *pid, const char *file,
       for (sig = 1; sig <= _NSIG; ++sig)
 	if (__sigismember (&attrp->__sd, sig) != 0
 	    && __sigaction (sig, &sa, NULL) != 0)
-	  _exit (SPAWN_ERROR);
+	  goto fail;
 
     }
 
@@ -150,25 +162,25 @@  __spawni (pid_t *pid, const char *file,
       == POSIX_SPAWN_SETSCHEDPARAM)
     {
       if (__sched_setparam (0, &attrp->__sp) == -1)
-	_exit (SPAWN_ERROR);
+	goto fail;
     }
   else if ((flags & POSIX_SPAWN_SETSCHEDULER) != 0)
     {
       if (__sched_setscheduler (0, attrp->__policy, &attrp->__sp) == -1)
-	_exit (SPAWN_ERROR);
+	goto fail;
     }
 #endif
 
   /* Set the process group ID.  */
   if ((flags & POSIX_SPAWN_SETPGROUP) != 0
       && __setpgid (0, attrp->__pgrp) != 0)
-    _exit (SPAWN_ERROR);
+    goto fail;
 
   /* Set the effective user and group IDs.  */
   if ((flags & POSIX_SPAWN_RESETIDS) != 0
       && (local_seteuid (__getuid ()) != 0
 	  || local_setegid (__getgid ()) != 0))
-    _exit (SPAWN_ERROR);
+    goto fail;
 
   /* Execute the file actions.  */
   if (file_actions != NULL)
@@ -196,7 +208,7 @@  __spawni (pid_t *pid, const char *file,
 		  if (action->action.close_action.fd < 0
 		      || action->action.close_action.fd >= fdlimit.rlim_cur)
 		    /* Signal the error.  */
-		    _exit (SPAWN_ERROR);
+		    goto fail;
 		}
 	      break;
 
@@ -209,7 +221,7 @@  __spawni (pid_t *pid, const char *file,
 
 		if (new_fd == -1)
 		  /* The `open' call failed.  */
-		  _exit (SPAWN_ERROR);
+		  goto fail;
 
 		/* Make sure the desired file descriptor is used.  */
 		if (new_fd != action->action.open_action.fd)
@@ -217,11 +229,11 @@  __spawni (pid_t *pid, const char *file,
 		    if (__dup2 (new_fd, action->action.open_action.fd)
 			!= action->action.open_action.fd)
 		      /* The `dup2' call failed.  */
-		      _exit (SPAWN_ERROR);
+		      goto fail;
 
 		    if (close_not_cancel (new_fd) != 0)
 		      /* The `close' call failed.  */
-		      _exit (SPAWN_ERROR);
+		      goto fail;
 		  }
 	      }
 	      break;
@@ -231,7 +243,7 @@  __spawni (pid_t *pid, const char *file,
 			  action->action.dup2_action.newfd)
 		  != action->action.dup2_action.newfd)
 		/* The `dup2' call failed.  */
-		_exit (SPAWN_ERROR);
+		goto fail;
 	      break;
 	    }
 	}
@@ -245,7 +257,7 @@  __spawni (pid_t *pid, const char *file,
       maybe_script_execute (file, argv, envp, xflags);
 
       /* Oh, oh.  `execve' returns.  This is bad.  */
-      _exit (SPAWN_ERROR);
+      goto fail;
     }
 
   /* We have to search for FILE on the path.  */
@@ -304,11 +316,15 @@  __spawni (pid_t *pid, const char *file,
 	  /* Some other error means we found an executable file, but
 	     something went wrong executing it; return the error to our
 	     caller.  */
-	  _exit (SPAWN_ERROR);
+	  goto fail;
 	    }
     }
   while (*p++ != '\0');
 
+ fail:
+  /* Send parent what was the reason of failure */
+  __write (pipefd[1], &errno, sizeof errno);
+  __close (pipefd[1]);
   /* Return with an error.  */
   _exit (SPAWN_ERROR);
 }