First of all, you should get system(3) out of the way; unlike what you're suggesting system(3) is not just fork+exec, but something quite complex, involving changing signal dispositions, waiting for the child and using /bin/sh as a wrapper (which may drop or add capabilities depending on its maintainer's whims and assumptions, mess with environment variables, source initialization scripts, and other funny things). Using just execv*(2) instead of system(3) will get all those spurious complications out of the way.
Second, you should have a deep look at the "Transformation of capabilities during execve()" part of the capabilities(7) manpage. I'm not going to copy-paste it here, but it basically boils down to: Capabilities are NOT inherited through execve() unless they're added to the ambient set of the thread (process), and they cannot be added there, unless they're already in the inheritable set of the thread. (The "inheritable" capabilities from the file's metadata are just a mask, limiting those of the thread).
So, in order the have the capabilities inherited through execve() you should a) copy them from the permitted to the inheritable set (which you could do with the capset(2) system call [1]) and b) add them to the ambient set (which you could do with prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE)).
Putting it all together:
$ cat capexec.c #include <sys/prctl.h> #include <unistd.h> #include <sys/syscall.h> #include <linux/capability.h> #include <err.h> int main(int ac, char **av){ static char *dav[] = { "/bin/bash", 0 }; struct __user_cap_header_struct hs; struct __user_cap_data_struct ds[2]; hs.version = 0x20080522; /*_LINUX_CAPABILITY_VERSION_3;*/ hs.pid = getpid(); if(syscall(SYS_capget, &hs, ds)) err(1, "capget"); ds[0].inheritable = ds[0].permitted; if(syscall(SYS_capset, &hs, ds)) err(1, "capset"); if(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_DAC_READ_SEARCH, 0, 0)) err(1, "prctl(pr_cap_ambient_raise)"); av = ac < 2 ? dav : av + 1; execvp(*av, av); err(1, "execvp %s", *av); } $ cc -Wall capexec.c -o capexec # as root # setcap cap_dac_read_search+ip /tmp/capexec $ ./capexec dd if=/dev/sda of=/dev/null count=1 1+0 records in 1+0 records out 512 bytes copied, 0.000299173 s, 1.7 MB/s
[1] the docs recommend using the libcap library; parts of this example were canibalized from a hack I've written for an old version of android, where there was no libcap, and many header definitions were missing. converting it to use the libcap wrappers is left as an exercise to the reader.