为什么CAP_NET_RAW不适用于SO_BINDTODEVICE?

问题描述:

我有以下简单的测试程序来创建一个UDP套接字,并将其绑定到一个特定的接口与SO_BINDTODEVICE,所以我可以然后bind()INADDR_ANY特别是在该接口上接收UDP广播。为什么CAP_NET_RAW不适用于SO_BINDTODEVICE?

//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); 
} 

当我运行此程序作为非root用户我得到以下输出:

$ ./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) 

,因为我不是rootSO_BINDTODEVICE是priviledged操作,这是非常符合逻辑的。但它被包含在能力CAP_NET_RAW当我从this snippet 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; 

好还是当我这样做:

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

我得到同样的错误输出:

$ ./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) 

和当然它的工作原理如下: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 

那么他们为什么不按预期工作?

该代码是正确的,getcap/setcap的用法是正确的,所以其他的东西必须阻止它的工作。

事实上,这是因为所有这些都是在/home/user中完成的,在此系统上安装了nosuid选件等等。

因此,简单地将二进制移动到例如/usr/bin/或其他未安装的部件nosuid将按照预期的方式工作。

(虽然您还需要CAP_NET_BIND_SERVICEbind()才能与端口333一起使用)