Matt S Matt S - 2 months ago 12x
Linux Question

File capabilities do not transfer to process once executed

I'm trying to write a program which requires elevated capabilities (rather than simply run it with sudo). However, none of the capabilities I set using setcap seem to transfer into the process once executed. This problem occurs across multiple executables and using different capabilities.

This code uses cap_set_file() to give the CAP_NET_RAW capability to a file passed as a CLA. (Don't ask me why I need this.)

#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/capability.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#define handle_error(msg) \
do { printf("%s: %s\n", msg, strerror(errno)); exit(EXIT_FAILURE); } while (0)

void print_cap_buf(cap_t cur) {
char *buf;
buf = cap_to_text(cur, NULL);
printf("%s\n", buf);

void get_and_print_cap_buf() {
cap_t cur = cap_get_proc();

int main(int argc, char *argv[]) {
cap_t file_cap;

printf("Process capabilities: ");
get_and_print_cap_buf(); // Print the current process capability list.

file_cap = cap_from_text("cap_net_raw=ep");
if (file_cap == NULL) handle_error("cap_from_text");

printf("Capabilities to set in file: "); print_cap_buf(file_cap);

if (argc == 2) {
if ( cap_set_file(argv[1], file_cap) != 0) handle_error("cap_set_file");
} else printf("No file specified.\n");

return 0;

After compiling with gcc:

gcc -Wall -pedantic -std=gnu99 test.c -o tt -lcap

I give it the capabilities with:

sudo setcap "cap_setfcap,cap_fowner,cap_net_raw=eip" tt

and using getcap tt, the output is:

$ getcap tt
tt = cap_fowner,cap_net_raw,cap_setfcap+eip

However, when I run the program, I get the following output (test-client is an executable which creates a raw Ethernet socket):

$ ./tt test-client
Process capabilities: =
Capabilities to set in file: = cap_net_raw+ep
cap_set_file: Operation not permitted

HOWEVER... when I run the program with sudo, all process capabilities come through just fine.

$ sudo ./tt test-client
Process capabilities: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,37+ep
Capabilities to set in file: = cap_net_raw+ep

and the target file "test-client" gets its capabilities set properly.

However, even with CAP_NET_RAW, the client fails on its socket() call with EPERM. I've tried setting CAP_NET_ADMIN in case it needed that as well; same issue. I've tried using CAP_SETPCAP on the program above; no dice. I'm fairly sure I've narrowed it down to some disconnect where the executable file's capabilities aren't getting into the running process.

What am I missing here?

EDIT, the next morning:

Okay, so I've done some more testing and it turns out this code works just fine on a Raspberry Pi. I'm running Lubuntu 16.04 with LXTerminal on my primary machine and that's the one that's failing. It fails inside LXTerminal and also in a text-only shell. Maybe it's an OS bug?

The Lubuntu machine (cat /proc/version):

Linux version 4.4.0-34-generic (buildd@lgw01-20) (gcc version 5.3.1 20160413 (Ubuntu 5.3.1-14ubuntu2.1) ) #53-Ubuntu SMP Wed Jul 27 16:06:39 UTC 2016

The pi:

Linux version 4.4.11-v7+ (dc4@dc4-XPS13-9333) (gcc version 4.9.3 (crosstool-NG crosstool-ng-1.22.0-88-g8460611) ) #888 SMP Mon May 23 20:10:33 BST 2016


Tested on a different machine with the same USB key I used to install. Slightly different /proc/version:

Linux version 4.4.0-31-generic (buildd@lgw01-16) (gcc version 5.3.1 20160413 (Ubuntu 5.3.1-14ubuntu2.1) ) #50-Ubuntu SMP Wed Jul 13 00:07:12 UTC 2016

Works fine. I'm so confused.


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

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 :) )