How to Execve a Process, Retaining Capabilities in Spite of Missing Filesystem-Based Capabilities

How to execve a process, retaining capabilities in spite of missing filesystem-based capabilities?

I am not saying that I recommend this for what you are doing, but here it is.

Extracted from the manual, There have been some changes. According to it: fork does not change capabilities. And now there is an ambient set added in Linux kernel 4.3, it seems that this is for what you are trying to do.

   Ambient (since Linux 4.3):
This is a set of capabilities that are preserved across an execve(2) of a program that is not privileged. The ambient capability set obeys the invariant that no capability can ever
be ambient if it is not both permitted and inheritable.

The ambient capability set can be directly modified using
prctl(2). Ambient capabilities are automatically lowered if
either of the corresponding permitted or inheritable
capabilities is lowered.

Executing a program that changes UID or GID due to the set-
user-ID or set-group-ID bits or executing a program that has
any file capabilities set will clear the ambient set. Ambient
capabilities are added to the permitted set and assigned to
the effective set when execve(2) is called.

A child created via fork(2) inherits copies of its parent's
capability sets. See below for a discussion of the treatment of
capabilities during execve(2).

Transformation of capabilities during execve()
During an execve(2), the kernel calculates the new capabilities of
the process using the following algorithm:

P'(ambient) = (file is privileged) ? 0 : P(ambient)

P'(permitted) = (P(inheritable) & F(inheritable)) |
(F(permitted) & cap_bset) | P'(ambient)

P'(effective) = F(effective) ? P'(permitted) : P'(ambient)

P'(inheritable) = P(inheritable) [i.e., unchanged]

where:

P denotes the value of a thread capability set before the
execve(2)

P' denotes the value of a thread capability set after the
execve(2)

F denotes a file capability set

cap_bset is the value of the capability bounding set (described
below).

A privileged file is one that has capabilities or has the set-user-ID
or set-group-ID bit set.

File capabilities do not transfer to process once executed

I finally got this to work, thanks to the information found here:

https://superuser.com/questions/865310/file-capabilities-setcap-not-being-applied-in-linux-mint-17-1

It turns out that my home directory is being mounted as nosuid, which disables all capability flags.

When running the program on a filesystem without nosuid, it works as expected.


For future readers: if you encounter this issue, make sure your filesystem is not mounted as nosuid. Using the mount command, check for the filesystem that matches where you're storing the data (in my case /home/user) and see if the nosuid flag is set.

$ mount
...
/home/.ecryptfs/user/.Private on /home/user type ecryptfs (rw,nosuid,nodev,relatime,ecryptfs_fnek_sig=***,ecryptfs_sig=***,ecryptfs_cipher=aes,ecryptfs_key_bytes=16,ecryptfs_unlink_sigs)

(It's an ecryptfs system, so if you selected "Encrypt my home directory" on the Ubuntu install you'll probably have this problem. I couldn't figure out a way to mount this as suid, and probably wouldn't want to anyway.)

I ended up making a new directory /code (it's my filesystem, I can do what I want) which is mounted on a different partition without nosuid.


It would be nice if the man pages for capabilities referenced this fact... (edit: patch submitted, it does now :) )

Linux inheritable capability cleared on program start

According to capabilities(7)

During an execve(2), the kernel calculates the new capabilities of the process using the following algorithm:

   P'(permitted) = (P(inheritable) & F(inheritable)) |
(F(permitted) & cap_bset)

P'(effective) = F(effective) ? P'(permitted) : 0

P'(inheritable) = P(inheritable) [i.e., unchanged]

P(inheritable) means the inheritable bit in the process, F(inheritable) is the inheritable bit of the file being executed. As you can see, F(inheritable) is only used to determine the permitted capabilities of the process, it's not used to determine the inheritable capabilities.

The inheritable capabilities of a file is ANDed with the inheritable capabilities of the process -- this allows you to use this flag to prevent some capabilities from being set in the new process, it's not used to add anything to the new process.

If you want to make the capablility inheritable from your process, you can call cap_set_proc(). When a capability is permitted, you're allowed to make it inheritable.

Linux capabilities to launch process as root from a user mode program in C++

Try this (parent.cc):

#include <iostream>
#include <sys/capability.h>
#include <unistd.h>

int main() {
cap_t caps = cap_get_proc();
cap_value_t val = CAP_SETUID;
cap_set_flag(caps, CAP_EFFECTIVE, 1, &val, CAP_SET);
if (cap_set_proc(caps)) {
perror("failed to raise cap_setuid");
exit(1);
}
if (setuid(0)) {
perror("unable to setuid");
exit(1);
}
execl("./child.sh", "child.sh", NULL);
std::cout << "didn't work, uid=" << getuid();
exit(1);
}

With this (child.sh):

#!/bin/bash
id -u

Build and set things up:

$ chmod +x child.sh
$ g++ -o parent parent.cc -lcap
$ sudo setcap cap_setuid=p ./parent

If you run ./parent it should work like this:

$ ./parent 
0

This example is single threaded. If your app is single threaded, it should be sufficient. If your program is multithreaded, you might need to explore something like libpsx.



Related Topics



Leave a reply



Submit