4

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?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
DDS
  • 2,340
  • 16
  • 34
  • 2
    Not an expert here but it seems that https://stackoverflow.com/questions/413807/is-there-a-way-for-non-root-processes-to-bind-to-privileged-ports-on-linux answered a similar problem. – Zaiborg Sep 21 '22 at 14:02
  • 2
    You can't _increase_ capabilities that you don't already have. You have to be `root` (superuser) or already have a given capability. You can only lower/remove one. – Craig Estey Sep 21 '22 at 14:02
  • 2
    If you are not `root` then there's no way of requesting a privilege that require `root` access. But, as `root` you can grant sub-processes or applications privileged access to certain features. In short: you need privileged access to give out privileges - unprivileged applications can't just grab privilege - that would undermine everything. – Jesper Juhl Sep 21 '22 at 14:03
  • @Zaiborg No, this isn't an issue about getting access to privileged port, it's about the use of capabilities, this is just an example to "test" if it works. – DDS Sep 21 '22 at 14:04
  • 1
    Capabilities are privileges. What you are trying to do is simply not allowed/possible - it would undermine the entire security model if it worked - then any unprivileged process could just grab whatever privilege they wanted - then what would be the point? – Jesper Juhl Sep 21 '22 at 14:04
  • Btw; capabilities work just fine. Par of the product I work on daily contains a process that starts as root, forks, drops privileges except one it needs to reboot the system on request, the parent exits and the child is left to do its work (and it can, but nothing else). – Jesper Juhl Sep 21 '22 at 14:16
  • Ex to start Apache on port 80, the first process is owned by root, and the child processes switch to the configured Apache user and group. There is no way to directly attach the process to port 80 with another user. So starting Apache is done via `sudo`. This applies to all other processes, this is just an example. – Nic3500 Sep 22 '22 at 02:18
  • It looks like you are compiling against the libcap tar file content. The -Ilibcap/libcap header files are private files to the library. You shouldn't need those. – Tinkerer Nov 07 '22 at 14:38

1 Answers1

1

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

Tinkerer
  • 865
  • 7
  • 9
  • Serious?! Fair question, I guess... Using the shared library in this way effectively applies the code in the library as an additional constraint on the overly broad `CAP_NET_BIND_SERVICE` permission. That is, the code in that tiny shared library limits its use to opening port 80 only. This is precisely what the POSIX.1e folk were thinking about when they were writing their (draft) standard. Code uses privilege, and audited code is all that should be allowed to do that. – Andrew G Morgan Sep 24 '22 at 23:37
  • Both you two, giving a privilege to bind to a system port to any unprivileged executable (via a shared library) is a security breach. Why do you recommend that? – relent95 Oct 02 '22 at 03:04
  • If you have the ability to run `sudo` this is not a breach at all. – Tinkerer Oct 02 '22 at 04:53
  • There is a widely held belief that the port-80 privilege restriction is a mistake. For example, https://ar.al/2022/08/30/dear-linux-privileged-ports-must-die/ . It mostly [confuses folk](https://stackoverflow.com/search?q=port+80+privilege). Containers [remove it](https://medium.com/@olivier.gaumond/why-am-i-able-to-bind-a-privileged-port-in-my-container-without-the-net-bind-service-capability-60972a4d5496). The [Fully Capable](https://sites.google.com/site/fullycapable/capable-shared-objects) article does need some discussion about why doing the same with setuid-root is asking for trouble... – Andrew G Morgan Oct 02 '22 at 18:26
  • 1
    @relent95 I've now added a long discussion of how [breachable this code](https://sites.google.com/site/fullycapable/capable-shared-objects?authuser=0#h.hsdol48evlfu) is in said article. – Andrew G Morgan Oct 09 '22 at 22:37