OpenBSD: Introduction to `execpromises` in the pledge()

Image credit goes to Aaron Bieber <abieber@openbsd.org>

Hello readers,

Today, I would like to introduce you all to the new update on pledge() system call.

In my previous post about pledge(1) system call, I have introduced you all about the implementation of pledge() system call and under the hood working of pledge, that is, the kernel level working of the pledge system call. But, this time, I will show you the new features of the pledge(2) and how to use them and why to use them.

First of all, let me clear one important thing that pledge(1) and pledge(2) both are different things according to the man page of OpenBSD. Because parenthesis numbers indicate sections of a man page, like, (2) for system calls etc. So, here, parenthesis numbers are only to differentiate between the old pledge and new pledge or improved pledge, that’s all, nothing related to the parenthesis numbers of man page of OpenBSD.

Here, what are pledge(1) and pledge(2):

pledge(1) → at present using in OpenBSD 6.2-stable.

pledge(2) → at present using in OpenBSD 6.3-current and will be in OpenBSD 6.3-stable.

On 11 December 2017, Theo de Raadt said:

List: openbsd-tech
Subject:
pledge execpromises
From:
Theo de Raadt <deraadt () openbsd ! org>
Date:
2017–12–11 21:20:51
Message-ID:
6735.1513027251 () cvs ! openbsd ! org
This will probably be committed in the next day or so.
The 2nd argument of pledge() becomes execpromises, which is what
will gets activated after execve.
There is also a small new feature called “error”, which causes
violating system calls to return -1 with ENOSYS rather than killing
the process. This must be used with EXTREME CAUTION because libraries
and programs are full of unchecked system calls. If you carry on past
one of these failures, your program is in uncharted territory and
risks of exploitation become high.
“error” is being introduced for a different reason: The pre-exec
process’s expectation of what the post-exec process will do might
mismatch, so “error” allows things like starting an editor which has
no network access or maybe other restrictions in the future…
cvsweb.openbsd.org

pledge(1):

#include <unistd.h>
int pledge(const char *promises, const char *paths[]);

Now, pledge(2):

#include <unistd.h>
int pledge(
const char *promises, const char *execpromises);

In above, pledge(2), the second parameter is for child process which is created by using execve() system call.

execpromises in pledge(2) are used to provide promises on child process which is invoked using the combination of execve() system call.

  • execve() system call:
#include <unistd.h>
 int execve(const char *file, char *const argv[], char *const envp[]);

Description:

Like all of the exec functions, execve replaces the calling process image with a new process image. This has the effect of running a new program with the process ID of the calling process. Note that a new process is not started; the new process image simply overlays the original process image. The execve function is most commonly used to overlay a process image that has been created by a call to the fork function.

Return value:

A successful call to execve does not have a return value because the new process image overlays the calling process image. However, a -1 is returned if the call to execve is unsuccessful.

Suppose, consider below example as an sample implementation:

#cat test_parent1.c
#include <stdio.h>
#include <unistd.h>
int main (int argc, char **argv)
{
if(pledge("stdio exec","stdio rpath") == -1)
{
err(1,"parent pledge");
}
        printf ("Parent: Hello, World!\n");
char *arg[] = { "./child", 0, 0, 0 };
execve(arg[0], &arg[0], NULL);
}

#cat test_child1.c
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char **argv)
{
printf("child process\n");
        while(1) {}
}
#gcc -o parent test_parent1.c
#gcc -o child test_child1.c
# ./parent &
[1] 80962
# Parent: Hello, World!
# dmesg|grep 80962
process_name: child pid: 80962 ps_pledge: 9
process_name: child pid: 80962 ps_pledge: 9

As from above code, we have seen that pledge value of the new execve image is 9, that is;

#cat sys/pledge.h
#define PLEDGE_RPATH 0x0000000000000001ULL /* allow open for read */

#define PLEDGE_STDIO 0x0000000000000008ULL /* operate on own pid */

pledge for new execve image:

(RPATH)              (STDIO)                (NEW PLEDGE) 
0x0000000000000001 | 0x0000000000000008 = 0x0000000000000009

So, I think it is better to use fork() then use execve() in the forked process, so that, execve will overlay forked process’s image, not parent’s image.

This update on the pledge system call is the one step further towards improving OS security in OpenBSD.

I have tried to cover as most of what I have learned, in case I have forgotten or missed something, please feel free to update me.
One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.