Process killed on self-exec after pledge+unveil

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Process killed on self-exec after pledge+unveil

int3resting27
Example program:

#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv) {
        char *const args[] = { "Z", NULL, };
        char *const env[] = { NULL, };

        if (argv[0][0] == 'Z') {
                printf("success\n");
                return (0);
        }

        unveil(argv[0], "x");
        pledge("exec", "stdio rpath");
        execve(argv[0], args, env);
}

Expected behavior: execve's itself then prints "success" and exits
Actual behavior: killed by SIGABRT when calling execve. It doesn't seem
to be pledge's usual SIGABRT because nothing gets logged to syslog like
pledge is supposed to do and core isn't dumped

What fixes it:
- execve-ing something other than argv[0]
- unveiling /usr/libexec/ld.so and /usr/lib with "r" permission
(along with the original unveil) before execve. Not unveiling /usr/lib
makes ld.so complain about not finding libc.so
- Removing the pledge or unveil or both
- Setting pledge execpromises to NULL

What doesn't fix it:
- Setting pledge promises to NULL
- Setting every single pledge promise (including "error") for both
of pledge's arguments
- Unveiling everything except /usr/libexec/ld.so with "rwxc" permissions
(Surprisingly ld.so works without /usr/lib this time...)
- execve-ing a symlink (killed with SIGABRT) or a hard link (execve
returns with ENOENT) to the same program
- Checking return codes. None of the functions return errors

Another interesting effect is that removing "rpath" from execpromises
also causes the SIGABRT when execve-ing any program, not just argv[0].
This should really be documented in the pledge manpage!

Reproduced on 6.6-stable and one day old -current on amd64

Reply | Threaded
Open this post in threaded view
|

Re: Process killed on self-exec after pledge+unveil

Theo de Raadt-2
int3resting27 <[hidden email]> wrote:

> Example program:
>
> #include <unistd.h>
> #include <stdio.h>
> int main(int argc, char **argv) {
> char *const args[] = { "Z", NULL, };
> char *const env[] = { NULL, };
>
> if (argv[0][0] == 'Z') {
> printf("success\n");
> return (0);
> }
>
> unveil(argv[0], "x");
> pledge("exec", "stdio rpath");
> execve(argv[0], args, env);
> }
>
> Expected behavior: execve's itself then prints "success" and exits

You are making quite a big an assumption, and it is wrong.

Reply | Threaded
Open this post in threaded view
|

Re: Process killed on self-exec after pledge+unveil

Sebastien Marie-3
In reply to this post by int3resting27
On Sun, Mar 08, 2020 at 12:04:07AM +0000, int3resting27 wrote:

> Example program:
>
> #include <unistd.h>
> #include <stdio.h>
> int main(int argc, char **argv) {
> char *const args[] = { "Z", NULL, };
> char *const env[] = { NULL, };
>
> if (argv[0][0] == 'Z') {
> printf("success\n");
> return (0);
> }
>
> unveil(argv[0], "x");
> pledge("exec", "stdio rpath");
> execve(argv[0], args, env);
> }
>
> Expected behavior: execve's itself then prints "success" and exits
> Actual behavior: killed by SIGABRT when calling execve. It doesn't seem
> to be pledge's usual SIGABRT because nothing gets logged to syslog like
> pledge is supposed to do and core isn't dumped

unveil(2) setting in the process isn't cleared on execve(2) due to execpromises
use. see https://github.com/openbsd/src/blob/master/sys/kern/kern_exec.c#L530

I am unsure if it is intented or not. but at some point, it has been discussed
if unveil(2) should be inherited or not (comments in kern_exec.c are trace of
that).

For now, you need to unveil(2) all files required to reexecute your file, or not
use execpromises.

Thanks.
--
Sebastien Marie

Reply | Threaded
Open this post in threaded view
|

Re: Process killed on self-exec after pledge+unveil

Theo de Raadt-2
Sebastien Marie <[hidden email]> wrote:

> On Sun, Mar 08, 2020 at 12:04:07AM +0000, int3resting27 wrote:
> > Example program:
> >
> > #include <unistd.h>
> > #include <stdio.h>
> > int main(int argc, char **argv) {
> > char *const args[] = { "Z", NULL, };
> > char *const env[] = { NULL, };
> >
> > if (argv[0][0] == 'Z') {
> > printf("success\n");
> > return (0);
> > }
> >
> > unveil(argv[0], "x");
> > pledge("exec", "stdio rpath");
> > execve(argv[0], args, env);
> > }
> >
> > Expected behavior: execve's itself then prints "success" and exits
> > Actual behavior: killed by SIGABRT when calling execve. It doesn't seem
> > to be pledge's usual SIGABRT because nothing gets logged to syslog like
> > pledge is supposed to do and core isn't dumped
>
> unveil(2) setting in the process isn't cleared on execve(2) due to execpromises
> use. see https://github.com/openbsd/src/blob/master/sys/kern/kern_exec.c#L530
>
> I am unsure if it is intented or not. but at some point, it has been discussed
> if unveil(2) should be inherited or not (comments in kern_exec.c are trace of
> that).
>
> For now, you need to unveil(2) all files required to reexecute your file, or not
> use execpromises.

Don't use execpromises.

The code for it will probably be deleted.

There were all sorts of mocking words about pledge not being carried over
through execve, so I provided a tool that would do it.

I tried to do it for $PAGER and $EDITOR in many programs.  It is
basically impossible to handle symbolic link traversal, as well as
the potential for the sub-program to want to do more.  And then get
killed.  So I introduced "error", and found out that some editors fail
just as hard as having been killed when they receive an unexpected error.

But I waited a bit longer, to see if anyone found a usage case and could
make it work.

Noone has.  The people who said pledge is wrong because it can't be maintained
over execve are wrong -- rather than understanding the workings of Unix they
are only thinking of their impossible desires.