如何让system()启动的echo子进程继承进程Capabilities权限?
问题背景
我以非root用户运行一个执行echo命令的进程,已通过以下命令为可执行文件设置必要的Capabilities:
setcap cap_net_admin,cap_dac_override,cap_sys_admin=eip test_capab
但由于system()会创建子进程,echo命令并未继承任何权限,导致出现如下错误:
cannot create /proc/sys/net/ipv4/conf/acl_log_all/forwarding: Permission denied Failed to set /proc/sys/net/ipv4/conf/acl_log_all/forwarding: No such file or directory (errno: 2)
我编写了一个测试程序复现该问题,代码尝试通过Capabilities修改/proc/sys/net/ipv4/conf/acl_log_all/forwarding,但遇到权限问题,测试代码如下:
#include <stdio.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <sched.h> #include <stdlib.h> #include <sys/prctl.h> #include <sys/capability.h> int ipv4_echo2() { const char *filepath = "/proc/sys/net/ipv4/conf/acl_log_all/forwarding"; char buf[256]; snprintf(buf, sizeof(buf), "echo 1 > %s", filepath); if (system(buf) != 0) { printf("Failed to set %s: %s (errno: %d)\n", filepath, strerror(errno), errno); if (errno == EACCES) { printf("Error: Permission denied (EACCES)\n"); } else if (errno == EPERM) { printf("Error: Operation not permitted (EPERM)\n"); } return 1; } else { printf("Successfully set %s to 1\n", filepath); } return 0; } int main() { cap_t caps = cap_get_proc(); printf("Capabilities: %s\n", cap_to_text(caps, NULL)); cap_value_t newcaps[] = { CAP_NET_ADMIN, CAP_DAC_OVERRIDE, CAP_SYS_ADMIN }; cap_set_flag(caps, CAP_INHERITABLE, 1, newcaps, CAP_SET); cap_set_proc(caps); printf("Capabilities after update: %s\n", cap_to_text(caps, NULL)); cap_free(caps); system("cat /proc/self/status | grep Cap"); ipv4_echo2(); return 0; }
核心问题:system()函数创建的子进程无法继承父进程设置的Capabilities,导致修改/proc/sys/net/ipv4/conf/acl_log_all/forwarding时出现权限问题。
解决方案
方法1:直接在进程内完成文件写入(推荐)
system()会启动shell,而shell默认会丢弃继承的Capabilities,最可靠的方式是跳过shell调用,直接用C标准函数完成写入操作,全程在同一个进程内执行,无需依赖Capabilities继承:
修改ipv4_echo2函数如下:
int ipv4_echo2() { const char *filepath = "/proc/sys/net/ipv4/conf/acl_log_all/forwarding"; int fd = open(filepath, O_WRONLY); if (fd == -1) { printf("Failed to open %s: %s (errno: %d)\n", filepath, strerror(errno), errno); return 1; } const char *content = "1\n"; ssize_t bytes_written = write(fd, content, strlen(content)); if (bytes_written == -1) { printf("Failed to write to %s: %s (errno: %d)\n", filepath, strerror(errno), errno); close(fd); return 1; } printf("Successfully set %s to 1\n", filepath); close(fd); return 0; }
方法2:让shell保留权限(不推荐,有安全风险)
如果必须使用system(),可以通过shell参数或给bash加Capabilities实现,但会扩大权限范围,仅适合测试场景:
- 调用
system()时使用bash -p参数(保留有效UID/GID对应的权限):
snprintf(buf, sizeof(buf), "bash -p -c 'echo 1 > %s'", filepath);
- 或者给bash添加对应Capabilities(生产环境绝对禁用):
sudo setcap cap_net_admin,cap_dac_override,cap_sys_admin=eip /bin/bash
方法3:用exec系列函数替代system()
exec系列函数直接执行指定程序,不启动shell,能确保子进程继承父进程的Capabilities(前提是父进程Inheritable Capabilities设置正确):
修改ipv4_echo2函数如下:
int ipv4_echo2() { const char *filepath = "/proc/sys/net/ipv4/conf/acl_log_all/forwarding"; pid_t pid = fork(); if (pid == -1) { perror("fork failed"); return 1; } if (pid == 0) { // 子进程中重定向stdout到目标文件 int fd = open(filepath, O_WRONLY); if (fd == -1) { perror("open failed"); exit(1); } dup2(fd, STDOUT_FILENO); close(fd); // 执行echo命令 execlp("echo", "echo", "1", NULL); // execlp返回则说明执行失败 perror("execlp failed"); exit(1); } else { // 父进程等待子进程执行完成 int status; waitpid(pid, &status, 0); if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { printf("Successfully set %s to 1\n", filepath); return 0; } else { printf("Failed to set %s\n", filepath); return 1; } } }
内容的提问来源于stack exchange,提问作者Suresh Nayak
相关产品推荐
相关产品推荐

