Use capabilities to open privileged ports without being root

1k Views Asked by At

I'm trying to open privileged ports (as an example to use libcap) without being root.

This is my code:

// http_capabilities.cpp

#include <iostream>
#ifdef CLIENT
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#endif

#ifdef SERVER

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#include <sys/capability.h>
#endif


#ifdef SERVER
cap_t CAP_init(){
    cap_t caps = cap_get_proc();
    cap_value_t val = CAP_NET_BIND_SERVICE;//CAP_SETUID;
    cap_set_flag(caps, CAP_EFFECTIVE, 1, &val, CAP_SET);
    if (cap_set_proc(caps)) {
        perror("failed to raise cap_net_bind_service");
        exit(1);
    }
    return caps;
}

void CAP_close(cap_t caps){
    
     if (cap_free(caps) == -1){
    /* handle error */;
        perror("CAPABILITY ERROR - cap_free_proc()");
    }

}
#endif

int server(uint16_t  PORT)
#ifdef SERVER
{
    int server_fd, new_socket, valread;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[1024] = { 0 };
    std::string app = "Hello from server";
    const char* hello = app.c_str();
    
  
    // Creating socket file descriptor
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0))
        == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
  
    // Forcefully attaching socket to the port 8080
    if (setsockopt(server_fd, SOL_SOCKET,
                   SO_REUSEADDR | SO_REUSEPORT, &opt,
                   sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
  
    // Forcefully attaching socket to the port 8080
    if (bind(server_fd, (struct sockaddr*)&address,
             sizeof(address))
        < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    if ((new_socket
         = accept(server_fd, (struct sockaddr*)&address,
                  (socklen_t*)&addrlen))
        < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }
    valread = read(new_socket, buffer, 1024);
    printf("%s\n", buffer);
    send(new_socket, hello, strlen(hello), 0);
    printf("Hello message sent\n");
    
  // closing the connected socket
    close(new_socket);
  // closing the listening socket
    shutdown(server_fd, SHUT_RDWR);
    return 0;
}
#else
{
    //non lo compilerà mai
    //serve la ifdef per non dover linkare la libcap anche col client
    return 0;
}
#endif

int client(uint16_t  PORT){

    int sock = 0, valread, client_fd;
    struct sockaddr_in serv_addr;
    std::string app = "Hello from client";
    const char* hello = app.c_str();
    char buffer[1024] = { 0 };
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }
  
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
  
    // Convert IPv4 and IPv6 addresses from text to binary
    // form
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)
        <= 0) {
        printf(
            "\nInvalid address/ Address not supported \n");
        return -1;
    }
  
    if ((client_fd
         = connect(sock, (struct sockaddr*)&serv_addr,
                   sizeof(serv_addr)))
        < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }
    send(sock, hello, strlen(hello), 0);
    printf("Hello message sent\n");
    valread = read(sock, buffer, 1024);
    printf("%s\n", buffer);
  
    // closing the connected socket
    close(client_fd);
    return 0;
}


int main(int argc, char const* argv[])
{
    if(argc != 2){
        std::cerr<< "wrong number of params passed, defaulting to port 80" <<argc<< std::endl;
    }

    uint16_t  port = 80;
    
    if(argc == 2)
    port = atoi(argv[1]); 
    
    #ifdef SERVER
    
    std::cout<< "server"<< std::endl;
    cap_t cap = CAP_init();
    server(port);
    CAP_close(cap);
    #endif
    
    #ifdef CLIENT
    std::cout<< "client"<< std::endl;
    client(port);
    #endif
}

I compile it with:

g++ -DSERVER http_capabilities.cpp -Ilibcap/libcap -Ilibcap/libcap/include/ -lcap -Llibcap/libcap -o server_exe
# and
g++ -DCLIENT http_capabilities.cpp -Ilibcap/libcap -Ilibcap/libcap/include/ -lcap -Llibcap/libcap -o server_exe

I run it as:

$ ./server_exe 88

I get:

failed to raise cap_net_bind_service: Operation not permitted

What am I doing wrong?

1

There are 1 best solutions below

5
Tinkerer On

You should be able to use a file capability to make this work:

$ sudo setcap cap_net_bind_service=p ./server_exe

Then, when you execute ./server_exe it will have just enough privilege to work.

$ ./server_exe 88

There is also a description of how to do this with a capable shared library on the Fully Capable libcap site. I'm not sure how serious that is as a method, it does work. It was mentioned in this answer: https://stackoverflow.com/a/69924486/5739452