I always wondered how Linux Capabilities work, so today I gave it a quick read. Summing up, classic permissions work like booleans: either you're root or you are not. Capabilities simply give you a more fine-granted control on what an user can do and what not.
How is this good? Well, say we've got a daemon which should serve through a TCP port below 1025. As you might know, this usually requires root permissions, thus one might be tempted to run the program as root. Unfortunately the program is bugged, offers an exploitation, and allows $BadGuy to run a shell on your beloved, non-sandboxed system. Do you want $BadGuy to have a root shell? Abort, Retry, Fail?.
Or we might set the CAP_NET_BIND_SERVICE
as permitted and effective
to the executable which will run your server. Nice pointers about what
that means: the capabilities manpage and the
cap_from_text manpage.
Quick example here, i.e. how I tried it.
First a refresh on Linux TCP sockets, which reminds me the
sequence: socket
, bind
, listen
, accept
. Then a bit of lazy code
for implementing an echo service:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <assert.h>
int main(int argc, char *argv[])
{
int sd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in baddr, raddr;
if (sd == -1) {
perror("Cannot socket");
exit(EXIT_FAILURE);
}
baddr.sin_family = AF_INET;
baddr.sin_port = htons(1023); // hence error, for permission
inet_aton("127.0.0.1", &baddr.sin_addr);
if (bind(sd, (struct sockaddr *)&baddr, sizeof(baddr)) == -1) {
close(sd);
perror("Cannot bind");
exit(EXIT_FAILURE);
}
if (listen(sd, 1) == -1) {
close(sd);
perror("Cannot listen");
exit(EXIT_FAILURE);
}
socklen_t alen;
int rsd = accept(sd, (struct sockaddr *)&raddr, &alen);
close(sd);
if (rsd == -1) {
perror("Cannot listen");
exit(EXIT_FAILURE);
}
size_t n;
char buffer[5];
buffer[4] = 0;
while ((n = read(rsd, buffer, sizeof(buffer) - 1)) > 0) {
printf("Echoing: %s", buffer);
write(rsd, buffer, n); // yeah, ignoring errors. I said lazy
}
if (n < 0) {
perror("Oh shit! Dinosaurs");
}
close(rsd);
exit(EXIT_SUCCESS);
}
One gcc -Wall
later, we've got our a.out
, which will fail as expected
if launced from a regular user:
$ ./a.out
Cannot bind: Permission denied
After we stop crying, we can try with the aforementioned
CAP_NET_BIND_SERVICE
capability:
$ su -c 'setcap cap_net_bind_service+pe ./a.out'
Password:
$
...and then simply run ./a.out
again with your regular user.
Since the executable has now CAP_NET_BIND_SERVICE
in the Permitted and
Effective sets, the process which runs it through execve
gains the
capability, and only that one.
getcap <filename>
lists the capability associated to an executable. For
instance you can try it with ./a.out
$ getcap a.out
a.out = cap_net_bind_service+ep
Or with the ping
executable, since it needs some additional permission,
but everyone wants to use ping without being root...
$ getcap `which ping`
/usr/bin/ping = cap_net_admin,cap_net_raw+ep
Uh, this post is not intended as a guide, it's just my way of sharing info with friends. It's nowhere a complete documentation on the subject, so RTFM. And happy hacking.