Who ordered libpsx?

For many years, libcap was distributed as a single library. It was a library with no concept of a POSIX-thread (pthread) and how Capabilities should work in a multi-threaded process. All of the programs included in the libcap distribution were single threaded so it never seemed to be of much concern.

Increasingly, however, multi-threaded programming is becoming the norm. In C/C++ web applications for example, individual threads and/or fibers are devoted to individual web requests. In other languages, such as Go, which have a more sophisticated and implicit threading model this issue is core to supporting capabilities at all. However, this write-up concerns the C language library, libcap.

Threaded applications if, say, they serve the default http/https ports, have to start by binding to them - which is a privileged operation. So in this and other cases libcap and libpthread were destined to coexist linked into a single binary.

In a traditional (pre-Capabilities) model of privilege, a binary that needs to perform a privileged startup operation, such as binding to a low numbered network port, would be setuid-root or launched by root. Its startup sequence would look something like this:

int main() {

    do_startup_with_privilege();

    setuid(UID_OF_NOBODY);  /* this drops privilege */

    serve_queries_without_privilege();

}

The most obvious way to ensure that the queries are not handled by privileged threads is to not launch any threads until after the setuid() call is performed. That way, each of the threads (which shares the memory footprint with the rest of the process) is cloned from a thread without any privilege. This is an easy way for an application to use libcap today to leverage Capabilities for a privileged start-up phase, and then via cap_set_proc(), instead of setuid(), drop all privilege before going on to service the unprivileged runtime. For applications that can be structured this way, all is good and there is nothing more to be said.

However, the more pervasive that the use of threads is, the more tricky it gets to enforce this simple paradigm for structuring a program. For example, let's say the start-up involves setting up a connection to a device driver. The device driver interface is implemented by a handy library that once initialized no longer needs privilege, but does need it to open some device files. Out of sight of the API for the library, its implementation (typically for performance) pthread_create()s a few worker threads to service user requests. In this situation, the setuid() call above is already downstream of some thread creation. As we will see below, even if these individual threads use libcap to drop the privilege they don't need, but not all of it, they are exploitable from other threads of the larger process. The more there is implicit threading, the more extensive the attack surface of a program becomes.

POSIX anticipated this class of program when defining their thread implementation. They realized that privilege and threads needed careful consideration. They specified the concept of POSIX semantics for exactly this situation. POSIX semantics require that the security context of a process be common to all the executing threads. The reason this is required is that because pthreads operate in a common execution footprint, it is possible (and widely appreciated as obvious), within the standard API of a program, for any thread to cause any other thread to execute arbitrary code.

Under Linux, this requirement is surprisingly subtle to implement. Linux's native thread implementation is not equivalent to a POSIX thread that observes POSIX semantics enforced by the kernel. So, the libpthread userspace code is required to work a little harder to maintain POSIX semantics for client programs.

The official POSIX privilege model is managed by UID values. If you have a UID value of 0, you have superuser privilege. If you don't you have no privilege. System files and access-control are managed by UID and GIDs (group IDs), so the traditional privilege model requires POSIX semantics for dealing with standard library functions that change these values for a process. In glibc, for example, there are 9 such functions:

setegid(), seteuid(), setgroups(), setgid(), setregid(), setreuid(), setresgid(), setresuid() and setuid().

The (glibc/nptl) libpthread implementation backs each of these ostensible system-calls with a wrapper that synchronizes their results on all threads of a running process. Calling one of these functions will thus perform the setting on each thread and maintain the invariant that the security context is shared by all threads. The implemented support for this is often referred to as nptl:setxid support. This support is buried inside the glibc implementation. It is a hidden internal implementation detail of glibc.

In this way, our simple sketch of a program, above, can naturally accommodate traditional POSIX privileged startup even when that startup launches threads.

But where does that leave libcap? This library implements an alternative to the traditional POSIX privilege model. (Although it is derived from a withdrawn POSIX.1e extension.) The short answer is that it leaves it out in the cold. All of the same considerations about process internal privilege escalation are true of Capabilities. To clearly show this, we have included an exploit example test in the libcap sources.

It is for this reason, we have created libpsx, and we include it with the libcap distribution. The libpsx library implements POSIX semantics for Linux system calls - for all system calls that might need it. Logically, if glibc had implemented a 6-argument supporting setxid() generic system call API, libpsx would not be needed. However, it didn't, so we created a two function API library, libpsx, to address this need.

Note, these concerns are not limited to the needs of libcap. They are also true of other Linux specific security features accessible from userspace, but we humbly offer libpsx to address this, and leave its application to these features as an exercise for the interested reader.

Further reading