Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
    cap_free(buf);
}

void get_and_print_cap_buf() {
    cap_t cur = cap_get_proc();
    print_cap_buf(cur);
    cap_free(cur);
}

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");

    cap_free(file_cap);
    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

EDIT AGAIN: --

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.

like image 884
Matt S Avatar asked Aug 16 '16 02:08

Matt S


People also ask

What are file capabilities?

File capabilities allow running a program with elevated privileges without the need to make that executable a setuid binary. Capabilities allow a more fine grained definition of privileges for a program.

How do you determine the process capabilities?

Process Capability for Non-Normal Data To calculate the process capability, it is necessary to understand the process data shapes, whether it is normal or non-normal, if it is non-normal data we must see that process is in control or not if it is not in control; then it cannot be predicted for process capability.

What are kernel capabilities?

Starting with kernel 2.2, Linux divides the privileges traditionally associated with superuser into distinct units, known as capabilities, which can be independently enabled and disabled. Capabilities are a per-thread attribute.

Where are capabilities stored?

Capabilities In Action All the capabilities for processes and threads are stored in the status file under the process/thread directory in the /proc file system. These properties start "Cap" name. Alternatively, for a running process, you can get the hex-encoded capabilities and then later decode it with capsh.


1 Answers

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

like image 70
Matt S Avatar answered Oct 13 '22 21:10

Matt S