I'm running a process as a non-root user that executes the echo
command. I have set the necessary capabilities on the executable using:
setcap cap_net_admin,cap_dac_override,cap_sys_admin=eip test_capab
However, since system()
creates a child process, the echo
command does not inherit any capabilities, resulting in the following error:
cannot create /proc/sys/net/ipv4/conf/acl_log_all/forwarding: Permission denied
Failed to set /proc/sys/net/ipv4/conf/acl_log_all/forwarding: No such file or directory (errno: 2)
I’ve written a sample test program to simulate the issue. The code attempts to modify /proc/sys/net/ipv4/conf/acl_log_all/forwarding
using capabilities, but it encounters permission issues. Here's the test code:
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/capability.h>
int ipv4_echo2() {
const char *filepath = "/proc/sys/net/ipv4/conf/acl_log_all/forwarding";
char buf[256];
snprintf(buf, sizeof(buf), "echo 1 > %s", filepath);
if (system(buf) != 0) {
printf("Failed to set %s: %s (errno: %d)\n", filepath, strerror(errno), errno);
if (errno == EACCES) {
printf("Error: Permission denied (EACCES)\n");
} else if (errno == EPERM) {
printf("Error: Operation not permitted (EPERM)\n");
}
return 1;
} else {
printf("Successfully set %s to 1\n", filepath);
}
return 0;
}
int main() {
cap_t caps = cap_get_proc();
printf("Capabilities: %s\n", cap_to_text(caps, NULL));
cap_value_t newcaps[] = { CAP_NET_ADMIN, CAP_DAC_OVERRIDE, CAP_SYS_ADMIN };
cap_set_flag(caps, CAP_INHERITABLE, 1, newcaps, CAP_SET);
cap_set_proc(caps);
printf("Capabilities after update: %s\n", cap_to_text(caps, NULL));
cap_free(caps);
system("cat /proc/self/status | grep Cap");
ipv4_echo2();
return 0;
}
The system()
function creates a child process that does not inherit the capabilities set for the parent process, leading to permission issues when trying to modify /proc/sys/net/ipv4/conf/acl_log_all/forwarding
.
Does anyone have suggestions on how I can ensure the necessary capabilities are inherited by the echo
process, or an alternative approach to accomplish this?
FWIW I recently wrote this article on Inheriting Privilege on the libcap
distribution website.
That being said, you don't need to inherit anything to get your program to work. Using system()
is what is creating the need for some sort of inheritance. If you go down that path, you can use Ambient capability inheritance for that--see the above article for details (and my 2024-09-15 update below for an example).
Here is a rewrite of your code that avoids any need for inheritance:
#include <stdio.h>
#include <stdlib.h>
#include <sys/capability.h>
int ipv4_echo2() {
const char *filepath = "/proc/sys/net/ipv4/conf/acl_log_all/forwarding";
FILE *f = fopen(filepath, "w");
if (f == NULL) {
perror("failed to open forwarding file");
return 1;
}
int n = fprintf(f, "1\n");
fclose(f);
if (n != 2) {
fprintf(stderr, "unable to write to %s\n", filepath);
return 1;
}
return 0;
}
int main() {
cap_t caps = cap_get_proc();
printf("Capabilities: %s\n", cap_to_text(caps, NULL));
cap_value_t newcaps[] = { CAP_NET_ADMIN, CAP_DAC_OVERRIDE, CAP_SYS_ADMIN };
cap_set_flag(caps, CAP_EFFECTIVE, 3, newcaps, CAP_SET);
if (cap_set_proc(caps) != 0) {
perror("unable to set caps");
exit(1);
}
cap_free(caps);
caps = cap_get_proc();
char *text = cap_to_text(caps, NULL);
printf("Capabilities after update: %s\n", text);
cap_free(text);
cap_free(caps);
int r = ipv4_echo2();
printf("setting %s\n", r ? "failed":"worked");
}
Then, to use the code:
$ gcc ex.c -lcap
$ ./a.out
Capabilities: =
unable to set caps: Operation not permitted
$ sudo setcap cap_net_admin,cap_dac_override,cap_sys_admin=p a.out
$ ./a.out
Capabilities: cap_dac_override,cap_net_admin,cap_sys_admin=p
Capabilities after update: cap_dac_override,cap_net_admin,cap_sys_admin=ep
setting worked
$
Update 2024-09-15:
The above shows how to achieve running some code, which you can read/audit as correct, and not leak privilege. Based on the comments, it sounds as if covering all the potential uses also means looking for something less locked down... (The system()
function is typically pretty easy to subvert using environment variables etc.) That being said, here is another way to achieve what you ask that leverages the Ambient capability inheritance property.
The libcap
library contains some functions for launching programs with different capabilities from the parent program. The manual page for cap_launch covers this. In principle you could use it instead of system()
as it cuts out the strict need for a shell intermediary. Here we show an example where the command is explicitly a bash command line, so you can also see how to do that if you need too:
#include <stdio.h>
#include <stdlib.h>
#include <sys/capability.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char **argv) {
cap_t c = cap_get_proc();
cap_fill(c, CAP_EFFECTIVE, CAP_PERMITTED);
if (cap_set_proc(c) != 0) {
perror("failed to raise effective capabilities");
exit(1);
}
cap_iab_t iab = cap_iab_init();
cap_iab_set_vector(iab, CAP_IAB_AMB, CAP_NET_ADMIN, CAP_SET);
const char *args[] = {"bash", "-c", "cat /proc/self/status | grep Cap", NULL};
cap_launch_t launch = cap_new_launcher("/bin/bash", args, NULL);
cap_launcher_set_iab(launch, iab);
pid_t pid = cap_launch(launch, NULL);
if (pid < 0) {
perror("failed to launch");
exit(1);
}
int status;
waitpid(pid, &status, 0);
if (!WIFEXITED(status)) {
perror("child failed");
exit(1);
}
cap_free(launch);
cap_free(c);
}
Call this code ex2.c
. Compile and run it as follows:
$ cc ex2.c -lcap
$ ./a.out
failed to launch: No child processes
$ sudo setcap cap_net_admin,cap_setpcap=p a.out
$ ./a.out
CapInh: 0000000000001000
CapPrm: 0000000000001000
CapEff: 0000000000001000
CapBnd: 000001ffffffffff
CapAmb: 0000000000001000
$
Note libcap-2.71+
, for this specific use case, won't require cap_setpcap
in that setcap
command-line. In cases where the chosen IAB tuple does suppress values from the Bounding capabilities, that capability is needed. Lazily, libcap-2.70
and earlier versions always required that capability as a result. This, and some mis-documented confusion about setting Ambient capabilities, was reported in this bug, so the next release of libcap
will fix that detail.