Why is CAP_NET_RAW not working with SO_BINDTODEVICE?

I have the following simple test program to create a UDP socket and bind it to a specific interface using SO_BINDTODEVICE , so I can then bind() do INADDR_ANY to receive UDP broadcasts specifically for this interface.

 //filename: bindtest.c #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <errno.h> #define MY_PORT (333) #define MY_DEVICE "enp0s3" #define BUFFERSIZE (1000) /* global variables */ int sock; struct sockaddr_in sa; struct sockaddr_in my_addr; char buffer[BUFFERSIZE]; int main(int argc, char *argv[]) { unsigned int echolen, clientlen; int rc, n; char opt_buffer[1000]; struct protoent *udp_protoent; struct timeval receive_timeout; int optval; socklen_t opt_length; sleep(1); /* Create the UDP socket */ if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { printf ("%s: failed to create UDP socket (%s) \n", argv[0], strerror(errno)); exit (EXIT_FAILURE); } printf ("UDP socket created\n"); /* set the recvfrom timeout value */ receive_timeout.tv_sec = 5; receive_timeout.tv_usec = 0; rc=setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &receive_timeout, sizeof(receive_timeout)); if (rc != 0) { printf ("%s: could not set SO_RCVTIMEO (%s)\n", argv[0], strerror(errno)); exit (EXIT_FAILURE); } printf ("set timeout to time [s]: %d time [ms]: %d\n", receive_timeout.tv_sec, receive_timeout.tv_usec); /* allow broadcast messages for the socket */ int true = 1; rc=setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &true, sizeof(true)); if (rc != 0) { printf ("%s: could not set SO_BROADCAST (%s)\n", argv[0], strerror(errno)); exit (EXIT_FAILURE); } printf ("set SO_BROADCAST worked\n"); /* bind to a specific interface */ char device[] = MY_DEVICE; rc=setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device)); if (rc != 0) { printf ("%s: could not set SO_BINDTODEVICE (%s)\n", argv[0], strerror(errno)); exit (EXIT_FAILURE); } printf ("SO_BINDTODEVICE worked\n"); /* bind my own Port */ my_addr.sin_family = AF_INET; my_addr.sin_addr.s_addr = INADDR_ANY; my_addr.sin_port = htons(MY_PORT); rc = bind (sock, (struct sockaddr *) &my_addr, sizeof(my_addr)); if (rc < 0) { printf ("%s: could not bind port (%s)\n", argv[0], strerror(errno)); exit (EXIT_FAILURE); } printf ("bind() worked\n"); sa.sin_family = AF_INET; sa.sin_addr.s_addr = INADDR_BROADCAST; sa.sin_port = htons(MY_PORT); char data[20]; sprintf(data,"FOOBAR"); int res = sendto(sock, &data, strlen(data), 0, (struct sockaddr*)&sa, sizeof(sa)); if(res < 0){ printf("could not send\n"); } else { printf("data sent\n"); } close(sock); printf ("socket closed\n"); exit(0); } 

when I run this program as a non-root user, I get the following output:

 $ ./bindtest UDP socket created set timeout to time [s]: 5 time [ms]: 0 set SO_BROADCAST worked ./bindtest: could not set SO_BINDTODEVICE (Operation not permitted) 

which is pretty logical, because I'm not root , and SO_BINDTODEVICE is a privileged operation. but it is included in the CAP_NET_RAW , as I understand from this piece of code from the Linux kernel :

 static int sock_setbindtodevice(struct sock *sk, char __user *optval, int optlen) { int ret = -ENOPROTOOPT; #ifdef CONFIG_NETDEVICES struct net *net = sock_net(sk); char devname[IFNAMSIZ]; int index; /* Sorry... */ ret = -EPERM; if (!ns_capable(net->user_ns, CAP_NET_RAW)) goto out; 

Ok when I do this:

 $ getcap bindtest $ sudo setcap cap_net_raw+ep bindtest $ getcap bindtest bindtest = cap_net_raw+ep 

I get the same error output:

 $ ./bindtest UDP socket created set timeout to time [s]: 5 time [ms]: 0 set SO_BROADCAST worked ./bindtest: could not set SO_BINDTODEVICE (Operation not permitted) 

And of course it works as root :

 $ sudo ./bindtest UDP socket created set timeout to time [s]: 5 time [ms]: 0 set SO_BROADCAST worked SO_BINDTODEVICE worked bind() worked data sent socket closed 

so why don't they work as expected?

+1
source share
1 answer

The correct code, using getcap / setcap correct, so something else should block this from working.

And really, because all this was done in /home/user , which is mounted on this system with the nosuid option among others.

So just moving the binary, for example /usr/bin/ or any other part that is not mounted, nosuid will work as expected in the first place.

(Although you also need CAP_NET_BIND_SERVICE for bind() to work with port 333, as in the example)

+2
source

Source: https://habr.com/ru/post/1242000/


All Articles