Re: Possible bug in lib/libc/gen/exec.c

2021-09-17 Thread Alejandro Colomar (man-pages)

Hello Theo,

On 9/16/21 10:53 PM, Theo de Raadt wrote:

@@ -45,25 +46,31 @@ execl(const char *name, const char *arg,
  {
va_list ap;
char **argv;
-   int n;
+   size_t maplen;
+   int save_errno, n, error;


See below.

  
  	va_start(ap, arg);

n = 1;
while (va_arg(ap, char *) != NULL)
n++;
va_end(ap);
-   argv = alloca((n + 1) * sizeof(*argv));
-   if (argv == NULL) {
-   errno = ENOMEM;
+   maplen = (n + 1) * sizeof(*argv);
+   argv = mmap(NULL, maplen, PROT_WRITE|PROT_READ,
+   MAP_ANON|MAP_PRIVATE, -1, 0);
+   if (argv == MAP_FAILED)
return (-1);
-   }
va_start(ap, arg);
n = 1;
argv[0] = (char *)arg;
while ((argv[n] = va_arg(ap, char *)) != NULL)
n++;
va_end(ap);
-   return (execve(name, argv, environ));
+
+   error = execve(name, argv, environ);
+   save_errno = errno;
+   munmap(argv, maplen);
+   errno = save_errno;
+   return (error);


This part could be simplified to:

(void) execve(name, argv, environ);
save_errno = errno;
munmap(argv, maplen);
errno = save_errno;
return -1;

Allowing to remove the error variable.  execve(2) always returns -1.


  }
  DEF_WEAK(execl);
  
@@ -72,18 +79,21 @@ execle(const char *name, const char *arg

  {
va_list ap;
char **argv, **envp;
-   int n;
+   size_t maplen;
+   int save_errno, n, error;


Same here.


@@ -91,7 +101,12 @@ execle(const char *name, const char *arg
n++;
envp = va_arg(ap, char **);
va_end(ap);
-   return (execve(name, argv, envp));
+
+   error = execve(name, argv, envp);
+   save_errno = errno;
+   munmap(argv, maplen);
+   errno = save_errno;
+   return error;


Same here


@@ -99,25 +114,32 @@ execlp(const char *name, const char *arg
  {
va_list ap;
char **argv;
-   int n;
+   size_t maplen;
+   int save_errno, n, error;


Same here.



--
Alejandro Colomar
Linux man-pages comaintainer; https://www.kernel.org/doc/man-pages/
http://www.alejandro-colomar.es/



Re: Possible bug in lib/libc/gen/exec.c

2021-09-16 Thread enh
we had the same issue in bionic when we removed all our alloca()s, modulo
the fact that ours is a VLA rather than alloca(), but same thing:
https://android.googlesource.com/platform/bionic/+/master/libc/bionic/exec.cpp#61

we argued that it doesn't matter in this case though because we'll touch
all the entries, in order, so there's no possibility of jumping off the end
of the stack onto another VMA.

(we have used mmap() instead in more complicated cases though.)

On Thu, Sep 16, 2021 at 1:39 PM Theo de Raadt  wrote:

> Alejandro Colomar (man-pages)  wrote:
>
> > Hi,
> >
> > I don't know if OpenBSD has a different implementation of alloca(3)
> > than Linux.  In Linux, alloca(3) (a.k.a. __builtin_alloca()) can't
> > return NULL, as it can't detect errors.
>
> There are no alloca can return NULL.
>
> > The only way to detect an
> > error is to add a handler for SIGSEGV, AFAIK.
>
> Actually, it is worse than that.  Massive alloca have been encountered
> which reach off the stack.  For over 20 years we've been on a mission
> to delete alloca in our tree, and these are the last ones that remain,
> because other allocators are very difficult to use in this place.
>
> Maybe we should investigate using mmap.  Of the 4 cases, 3 are not
> too difficult, but the 4th case will be very messy, including unwind
> for the 3rd case.
>
> > I found the following code in :
> >
> >   argv = alloca((n + 1) * sizeof(*argv));
> >   if (argv == NULL) {
> >   errno = ENOMEM;
> >   return (-1);
> >   }
> >
> > But of course, that NULL should never happen, right?
>
> > As a side note, I think you could encapsulate the code calling
> > alloca(3) into a helper macro, to avoid repetition.
>
> I don't see how that would help anything.
>
> > I can prepare a patch if you confirm the bug.
>
> What do you propose to do in your patch?
>
>
>


Re: Possible bug in lib/libc/gen/exec.c

2021-09-16 Thread Theo de Raadt
It always returns -1 until the world changes in some subtle way,
then the code is wrong.

The logic is supposed to return what execve returns, not reinvent
the value.

Over decades this kind of assumption can turn into a bug, so I
prefer to do it right.

Alejandro Colomar (man-pages)  wrote:

> Hello Theo,
> 
> On 9/16/21 10:53 PM, Theo de Raadt wrote:
> > @@ -45,25 +46,31 @@ execl(const char *name, const char *arg,
> >   {
> > va_list ap;
> > char **argv;
> > -   int n;
> > +   size_t maplen;
> > +   int save_errno, n, error;
> 
> See below.
> 
> > va_start(ap, arg);
> > n = 1;
> > while (va_arg(ap, char *) != NULL)
> > n++;
> > va_end(ap);
> > -   argv = alloca((n + 1) * sizeof(*argv));
> > -   if (argv == NULL) {
> > -   errno = ENOMEM;
> > +   maplen = (n + 1) * sizeof(*argv);
> > +   argv = mmap(NULL, maplen, PROT_WRITE|PROT_READ,
> > +   MAP_ANON|MAP_PRIVATE, -1, 0);
> > +   if (argv == MAP_FAILED)
> > return (-1);
> > -   }
> > va_start(ap, arg);
> > n = 1;
> > argv[0] = (char *)arg;
> > while ((argv[n] = va_arg(ap, char *)) != NULL)
> > n++;
> > va_end(ap);
> > -   return (execve(name, argv, environ));
> > +
> > +   error = execve(name, argv, environ);
> > +   save_errno = errno;
> > +   munmap(argv, maplen);
> > +   errno = save_errno;
> > +   return (error);
> 
> This part could be simplified to:
> 
>   (void) execve(name, argv, environ);
>   save_errno = errno;
>   munmap(argv, maplen);
>   errno = save_errno;
>   return -1;
> 
> Allowing to remove the error variable.  execve(2) always returns -1.
> 
> >   }
> >   DEF_WEAK(execl);
> >   @@ -72,18 +79,21 @@ execle(const char *name, const char *arg
> >   {
> > va_list ap;
> > char **argv, **envp;
> > -   int n;
> > +   size_t maplen;
> > +   int save_errno, n, error;
> 
> Same here.
> 
> > @@ -91,7 +101,12 @@ execle(const char *name, const char *arg
> > n++;
> > envp = va_arg(ap, char **);
> > va_end(ap);
> > -   return (execve(name, argv, envp));
> > +
> > +   error = execve(name, argv, envp);
> > +   save_errno = errno;
> > +   munmap(argv, maplen);
> > +   errno = save_errno;
> > +   return error;
> 
> Same here
> 
> > @@ -99,25 +114,32 @@ execlp(const char *name, const char *arg
> >   {
> > va_list ap;
> > char **argv;
> > -   int n;
> > +   size_t maplen;
> > +   int save_errno, n, error;
> 
> Same here.
> 
> 
> 
> -- 
> Alejandro Colomar
> Linux man-pages comaintainer; https://www.kernel.org/doc/man-pages/
> http://www.alejandro-colomar.es/



Re: Possible bug in lib/libc/gen/exec.c

2021-09-16 Thread Theo de Raadt
enh  wrote:

> we had the same issue in bionic when we removed all our alloca()s, modulo the 
> fact
> that ours is a VLA rather than alloca(), but same thing:
> https://android.googlesource.com/platform/bionic/+/master/libc/bionic/exec.cpp#61

that cargo culting doesn't fix anything...

> we argued that it doesn't matter in this case though because we'll touch all 
> the
> entries, in order, so there's no possibility of jumping off the end of the 
> stack onto
> another VMA.

we've always had a sufficient gap beyond the end of our stack, but a few
years ago many people freaked out because they didn't.  nowadays, that is
enforced on a system level, so that door is largely closed, dependent on
linear access.

> (we have used mmap() instead in more complicated cases though.)

yes, that is the solution, but rollback and errno must be handled carefully.

If this mmap() fix goes in, the only alloca() remain in our tree are:

   clang - seems to be in test code only
   binutils - shocking
   1 in gcc configure
   1 in perl - non-openbsd cases
   1 small one in libcrypto - someone should go fix that



Re: Possible bug in lib/libc/gen/exec.c

2021-09-16 Thread Theo de Raadt
Theo de Raadt  wrote:

> Maybe we should investigate using mmap.  Of the 4 cases, 3 are not
> too difficult, but the 4th case will be very messy, including unwind
> for the 3rd case.

Here is a version that uses mmap instead of alloca, including rollback
of resource allocations in case of failure.  This is similar to the mmap
use in vfprintf which I added many years ago to support positional
arguments without alloca or malloc, so that snprintf family can be
remain maximally reentrant.  So I've been here before...

I have done a little testing, but it may still have bugs.

Index: lib/libc/gen/exec.c
===
RCS file: /cvs/src/lib/libc/gen/exec.c,v
retrieving revision 1.23
diff -u -p -u -r1.23 exec.c
--- lib/libc/gen/exec.c 13 Mar 2016 18:34:20 -  1.23
+++ lib/libc/gen/exec.c 16 Sep 2021 20:48:25 -
@@ -30,6 +30,7 @@
 
 #include 
 #include 
+#include 
 
 #include 
 #include 
@@ -45,25 +46,31 @@ execl(const char *name, const char *arg,
 {
va_list ap;
char **argv;
-   int n;
+   size_t maplen;
+   int save_errno, n, error;
 
va_start(ap, arg);
n = 1;
while (va_arg(ap, char *) != NULL)
n++;
va_end(ap);
-   argv = alloca((n + 1) * sizeof(*argv));
-   if (argv == NULL) {
-   errno = ENOMEM;
+   maplen = (n + 1) * sizeof(*argv);
+   argv = mmap(NULL, maplen, PROT_WRITE|PROT_READ,
+   MAP_ANON|MAP_PRIVATE, -1, 0);
+   if (argv == MAP_FAILED)
return (-1);
-   }
va_start(ap, arg);
n = 1;
argv[0] = (char *)arg;
while ((argv[n] = va_arg(ap, char *)) != NULL)
n++;
va_end(ap);
-   return (execve(name, argv, environ));
+
+   error = execve(name, argv, environ);
+   save_errno = errno;
+   munmap(argv, maplen);
+   errno = save_errno;
+   return (error);
 }
 DEF_WEAK(execl);
 
@@ -72,18 +79,21 @@ execle(const char *name, const char *arg
 {
va_list ap;
char **argv, **envp;
-   int n;
+   size_t maplen;
+   int save_errno, n, error;
 
va_start(ap, arg);
n = 1;
while (va_arg(ap, char *) != NULL)
n++;
va_end(ap);
-   argv = alloca((n + 1) * sizeof(*argv));
-   if (argv == NULL) {
-   errno = ENOMEM;
+
+   maplen = (n + 1) * sizeof(*argv);
+   argv = mmap(NULL, maplen, PROT_WRITE|PROT_READ,
+   MAP_ANON|MAP_PRIVATE, -1, 0);
+   if (argv == MAP_FAILED)
return (-1);
-   }
+
va_start(ap, arg);
n = 1;
argv[0] = (char *)arg;
@@ -91,7 +101,12 @@ execle(const char *name, const char *arg
n++;
envp = va_arg(ap, char **);
va_end(ap);
-   return (execve(name, argv, envp));
+
+   error = execve(name, argv, envp);
+   save_errno = errno;
+   munmap(argv, maplen);
+   errno = save_errno;
+   return error;
 }
 
 int
@@ -99,25 +114,32 @@ execlp(const char *name, const char *arg
 {
va_list ap;
char **argv;
-   int n;
+   size_t maplen;
+   int save_errno, n, error;
 
va_start(ap, arg);
n = 1;
while (va_arg(ap, char *) != NULL)
n++;
va_end(ap);
-   argv = alloca((n + 1) * sizeof(*argv));
-   if (argv == NULL) {
-   errno = ENOMEM;
+
+   maplen = (n + 1) * sizeof(*argv);
+   argv = mmap(NULL, maplen, PROT_WRITE|PROT_READ,
+   MAP_ANON|MAP_PRIVATE, -1, 0);
+   if (argv == MAP_FAILED)
return (-1);
-   }
+
va_start(ap, arg);
n = 1;
argv[0] = (char *)arg;
while ((argv[n] = va_arg(ap, char *)) != NULL)
n++;
va_end(ap);
-   return (execvp(name, argv));
+   error = execvp(name, argv);
+   save_errno = errno;
+   munmap(argv, maplen);
+   errno = save_errno;
+   return error;
 }
 
 int
@@ -132,10 +154,12 @@ execvpe(const char *name, char *const *a
 {
char **memp;
int cnt;
-   size_t lp, ln, len;
+   size_t lp, ln, curlen;
char *p;
int eacces = 0;
char *bp, *cur, *path, buf[PATH_MAX];
+   size_t maplen;
+   int save_errno, n;
 
/*
 * Do not allow null name
@@ -156,13 +180,14 @@ execvpe(const char *name, char *const *a
/* Get the path we're searching. */
if (!(path = getenv("PATH")))
path = _PATH_DEFPATH;
-   len = strlen(path) + 1;
-   cur = alloca(len);
-   if (cur == NULL) {
-   errno = ENOMEM;
+
+   curlen = strlen(path) + 1;
+   cur = mmap(NULL, curlen, PROT_WRITE|PROT_READ,
+   MAP_ANON|MAP_PRIVATE, -1, 0);
+   if (cur == MAP_FAILED)
return (-1);
-   }
-   strlcpy(cur, path, len);
+
+   strlcpy(cur, path, curlen);
path = cur;
while ((p = strsep(, ":"))) {
 

Re: Possible bug in lib/libc/gen/exec.c

2021-09-16 Thread Theo de Raadt
Alejandro Colomar (man-pages)  wrote:

> Hi,
> 
> I don't know if OpenBSD has a different implementation of alloca(3)
> than Linux.  In Linux, alloca(3) (a.k.a. __builtin_alloca()) can't
> return NULL, as it can't detect errors.

There are no alloca can return NULL.

> The only way to detect an
> error is to add a handler for SIGSEGV, AFAIK.

Actually, it is worse than that.  Massive alloca have been encountered
which reach off the stack.  For over 20 years we've been on a mission
to delete alloca in our tree, and these are the last ones that remain,
because other allocators are very difficult to use in this place.

Maybe we should investigate using mmap.  Of the 4 cases, 3 are not
too difficult, but the 4th case will be very messy, including unwind
for the 3rd case.

> I found the following code in :
> 
>   argv = alloca((n + 1) * sizeof(*argv));
>   if (argv == NULL) {
>   errno = ENOMEM;
>   return (-1);
>   }
> 
> But of course, that NULL should never happen, right?

> As a side note, I think you could encapsulate the code calling
> alloca(3) into a helper macro, to avoid repetition.

I don't see how that would help anything.
 
> I can prepare a patch if you confirm the bug.

What do you propose to do in your patch?