非Root用户使用libcap开启特权端口失败,请求排查问题
问题
我尝试以非Root用户身份,借助libcap库开启特权端口(以此为例使用libcap),代码如下:
// 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 }
编译命令:
g++ -DSERVER http_capabilities.cpp -Ilibcap/libcap -Ilibcap/libcap/include/ -lcap -Llibcap/libcap -o server_exe # 客户端编译 g++ -DCLIENT http_capabilities.cpp -Ilibcap/libcap -Ilibcap/libcap/include/ -lcap -Llibcap/libcap -o client_exe
运行命令:
$ ./server_exe 88
得到错误提示:
failed to raise cap_net_bind_service: Operation not permitted
请问哪里操作有误?
解决方法
核心错误原因:普通进程默认没有修改自身capabilities的权限,你现在的代码是在程序运行时尝试自行添加
CAP_NET_BIND_SERVICE,但只有root进程或预先设置了对应capabilities属性的程序文件,才能修改进程的权限集合。修正步骤:
给编译好的
server_exe添加文件级capability:sudo setcap cap_net_bind_service=+ep ./server_exe这个命令给可执行文件赋予了**有效(Effective)和允许(Permitted)**的
CAP_NET_BIND_SERVICE权限,非root用户运行时,进程会自动继承这个权限。调整代码逻辑:
因为文件已经预先赋予权限,进程启动时Permitted集合里已经包含CAP_NET_BIND_SERVICE,你只需要把它加入Effective集合即可,或者直接删除手动设置权限的代码——因为+ep标识会让进程启动时自动激活该权限。简化后的
CAP_init函数可以改成:cap_t CAP_init(){ cap_t caps = cap_get_proc(); cap_value_t val = CAP_NET_BIND_SERVICE; // 将Permitted中的权限加入Effective集合 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; }验证:用非root用户重新运行
./server_exe 88,此时应该能正常绑定特权端口。
额外说明:
- 客户端编译时可以去掉
-lcap参数,因为客户端代码里的libcap相关部分被#ifdef SERVER包裹,不会被编译,不需要链接该库。 - 特权端口指端口号小于1024的端口,88属于这个范围,所以需要对应权限。
- 客户端编译时可以去掉
内容的提问来源于stack exchange,提问作者DDS
相关产品推荐
相关产品推荐

