Linux 下 C++ 设置子进程 capability、切换用户、execl调用

折腾半天,又是问deepseek又是问朋友,终于解决了。

Linux 有一个能力(capability)机制,相当于是对root权限的细分,你可以把这些权限细分给进程或程序。能力的介绍可以看看这个博客,我就不多说了。

出于安全考虑,我需要给子进程设置能力,同时又要切换到普通用户,再用execl执行别的程序。结果搞半天都没搞定。

关键的地方在于,execl调用可执行文件后的能力,既取决于进程本身,也取决于可执行文件的能力设置。我进行了一些测试,终于是搞定了。以下是一个调用子进程后通过execl执行设定系统时间的程序的例子。设定系统时间需要root权限,或者 CAP_SYS_TIME能力。测试的操作系统为 CentOS 7.6

设定时间的程序

首先,我们执行timedatectl set-ntp false关闭系统自动校时。

然后,写一个设定时间、打印时间然后睡觉的C++程序。

#include <ctime>
#include <iostream>
#include <sys/time.h>
#include <unistd.h>
// Function to set the system time
void SetSystemTime(const struct tm& newTime) {
 struct timeval tv;
 tv.tv_sec = mktime(const_cast<struct tm*>(&newTime)); // Convert struct tm to time_t
 tv.tv_usec = 0;
 if (settimeofday(&tv, nullptr) != 0) {
 perror("Failed to set system time");
 } else {
 std::cout << "System time successfully updated." << std::endl;
 }
}
void GetSystemTime() {
 time_t now = time(nullptr);
 struct tm* currentTime = localtime(&now);
 std::cout << "Current system time: "
 << (currentTime->tm_year + 1900) << "-"
 << (currentTime->tm_mon + 1) << "-"
 << currentTime->tm_mday << " "
 << currentTime->tm_hour << ":"
 << currentTime->tm_min << ":"
 << currentTime->tm_sec << std::endl;
}
int main() {
 struct tm newTime = {};
 // Set your desired time here
 newTime.tm_year = 2025 - 1900; // Year since 1900
 newTime.tm_mon = 3 - 1; // Month (0-11)
 newTime.tm_mday = 9; // Day of the month
 newTime.tm_hour = 10; // Hour (0-23)
 newTime.tm_min = 30; // Minute (0-59)
 newTime.tm_sec = 0; // Second (0-59)
 SetSystemTime(newTime);
 GetSystemTime();
 sleep(999);
 return 0;
}

编译:

g++ ./setTime.cpp -std=c++11 -o setTime.bin

用sudo运行:

[mario@vbox CPP]$ ./setTime.bin
Failed to set system time: Operation not permitted
Current system time: 2025-3-9 17:1:38
^C
[mario@vbox CPP]$ sudo ./setTime.bin
System time successfully updated.
Current system time: 2025-3-9 10:30:0

运行正确,我们再写一个在子进程中切换用户后设置时间的程序。

子进程切换用户后设置时间

#include <pwd.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <ctime>
#include <iostream>
// Function to set the system time
void SetSystemTime(const struct tm& newTime) {
 struct timeval tv;
 tv.tv_sec = mktime(const_cast<struct tm*>(&newTime)); // Convert struct tm to time_t
 tv.tv_usec = 0;
 if (settimeofday(&tv, nullptr) != 0) {
 perror("Failed to set system time");
 } else {
 std::cout << "System time successfully updated." << std::endl;
 }
}
void GetSystemTime() {
 time_t now = time(nullptr);
 struct tm* currentTime = localtime(&now);
 std::cout << "Current system time: "
 << (currentTime->tm_year + 1900) << "-"
 << (currentTime->tm_mon + 1) << "-"
 << currentTime->tm_mday << " "
 << currentTime->tm_hour << ":"
 << currentTime->tm_min << ":"
 << currentTime->tm_sec << std::endl;
}
void SetChildCapabilities() {
 cap_t caps;
 cap_value_t cap_list[2];
 // Initialize the capability set
 caps = cap_get_proc();
 // Set the capabilities
 cap_list[0] = CAP_NET_BIND_SERVICE; // Allow binding to ports < 1024
 cap_list[1] = CAP_SYS_TIME; // Allow setting system time
 if (cap_set_flag(caps, CAP_PERMITTED, 2, cap_list, CAP_SET) == -1 ||
 cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_list, CAP_SET) == -1 ||
 cap_set_flag(caps, CAP_INHERITABLE, 2, cap_list, CAP_SET) == -1) {
 std::cerr << "Failed to set capabilities.\n";
 exit(EXIT_FAILURE);
 }
 if (cap_set_proc(caps) == -1) {
 std::cerr << "Failed to apply capabilities.\n";
 exit(EXIT_FAILURE);
 }
 cap_free(caps);
}
// Function to preserve capabilities across exec
void PreserveCapabilities() {
 if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
 perror("Failed to set PR_SET_KEEPCAPS");
 } else {
 std::cout << "Capabilities will be preserved across exec." << std::endl;
 }
}
void SetParentInhCapabilities() {
 cap_t caps;
 cap_value_t cap_list[2];
 // Initialize the capability set
 caps = cap_get_proc();
 // Set the capabilities
 cap_list[0] = CAP_NET_BIND_SERVICE; // Allow binding to ports < 1024
 cap_list[1] = CAP_SYS_TIME; // Allow setting system time
 if (cap_set_flag(caps, CAP_INHERITABLE, 2, cap_list, CAP_SET) == -1) {
 std::cerr << "Failed to set capabilities.\n";
 exit(EXIT_FAILURE);
 }
 if (cap_set_proc(caps) == -1) {
 std::cerr << "Failed to apply capabilities.\n";
 exit(EXIT_FAILURE);
 }
 cap_free(caps);
}
void ChangeUid(uid_t new_uid) {
 if (setuid(new_uid) == -1) {
 std::cerr << "Failed to change UID.\n";
 exit(EXIT_FAILURE);
 }
}
void PrintCapabilities() {
 cap_t caps = cap_get_proc();
 if (caps == nullptr) {
 std::cerr << "Failed to get capabilities.\n";
 exit(EXIT_FAILURE);
 }
 char* cap_text = cap_to_text(caps, nullptr);
 if (cap_text == nullptr) {
 std::cerr << "Failed to convert capabilities to text.\n";
 cap_free(caps);
 exit(EXIT_FAILURE);
 }
 std::cout << "Process capabilities: " << cap_text << std::endl;
 cap_free(caps);
 cap_free(cap_text);
}
int main() {
 // Set capabilities for the parent process
 SetParentInhCapabilities();
 pid_t pid = fork();
 if (pid == -1) {
 std::cerr << "Failed to fork process.\n";
 return EXIT_FAILURE;
 } else if (pid == 0) {
 // Child process
 std::cout << "Child process PID: " << getpid() << std::endl;
 PreserveCapabilities();
 // Set capabilities for the child process
 SetChildCapabilities();
 // Change UID for the child process
 struct passwd* pw = getpwnam("nobody");
 if (pw == nullptr) {
 std::cerr << "Failed to get UID for 'nobody'.\n";
 return EXIT_FAILURE;
 }
 ChangeUid(pw->pw_uid);
 // Set capabilities of the child process
 SetChildCapabilities();
 // Print capabilities in the child process
 PrintCapabilities();
 // Set the new system time
 struct tm newTime = {};
 // Set your desired time here
 newTime.tm_year = 2025 - 1900; // Year since 1900
 newTime.tm_mon = 3 - 1; // Month (0-11)
 newTime.tm_mday = 9; // Day of the month
 newTime.tm_hour = 10; // Hour (0-23)
 newTime.tm_min = 30; // Minute (0-59)
 newTime.tm_sec = 0; // Second (0-59)
 SetSystemTime(newTime);
 GetSystemTime();
 sleep(999);
 } else {
 // Parent process
 std::cout << "Parent process PID: " << getpid() << std::endl;
 // Print capabilities in the parent process
 PrintCapabilities();
 wait(NULL); // Wait for the child process to finish
 }
 return EXIT_SUCCESS;
}

确认你的系统中有nobody这个用户,否则使用这个命令添加:sudo adduser nobody

这个程序的执行结果如下:

[mario@vbox CPP]$ g++ ./main.cpp -std=c++11 -lcap && sudo ./a.out &
[7] 19342
[mario@vbox CPP]$ Parent process PID: 19357
Process capabilities: = cap_net_bind_service,cap_sys_time+eip cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,35,36+ep
Child process PID: 19358
Capabilities will be preserved across exec.
Process capabilities: = cap_net_bind_service,cap_sys_time+eip cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,35,36+p
System time successfully updated.
Current system time: 2025-3-9 10:30:0

最后两行告诉我们,时间设定成功。

查询也显示Cap设置正常:

[mario@vbox CPP]$ cat /proc/19358/status | grep Cap
CapInh:	0000000002000400
CapPrm:	0000001fffffffff
CapEff:	0000000002000400
CapBnd:	0000001fffffffff
CapAmb:	0000000000000000
[mario@vbox CPP]$ capsh --decode=0000000002000400
0x0000000002000400=cap_net_bind_service,cap_sys_time
[mario@vbox CPP]$ getpcaps 19358
Capabilities for `19358': = cap_net_bind_service,cap_sys_time+eip cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,35,36+p

execl 调用

一直到这里,整个功能实现看上去都很简单,但是为什么之前我调不通呢?真是蛋疼,之前我都是在子进程里用execl拉起别的程序,然后再查询的,结果查询出来CapPrm和CapEff都是0,问题就在这execl里,execl 执行的是一个程序,而最终的cap结果是由进程和这个程序文件本身的cap共同决定的!关于exec,可以参考这个博客

我们使用P代表执行exec前的capabilities,P’代表执行exec后的capabilities,F代表exec执行的文件的capabilities。那么:

P’(Permitted) = (P(Inheritable) & F(Inheritable)) | (F(Permitted) & cap_bset)

P’(Effective) = F(Effective) ? P’(Permitted) : 0

P’(Inheritable) = P(Inheritable)

执行 setcap,设置程序文件本身的cap能力。如下所示,给它设置能力为ie后,用普通用户直接运行是无法获得CAP_SYS_TIME权限的。如果设置成ep,普通用户直接执行也能获得权限。如果设置为e,实测getcap不会输出任何权限。

[mario@vbox CPP]$ sudo setcap CAP_SYS_TIME+ie ./setTime.bin
[mario@vbox CPP]$ getcap ./setTime.bin
./setTime.bin = cap_sys_time+ei
[mario@vbox CPP]$ ./setTime.bin
setTime.bin:
Process capabilities: =
Failed to set system time: Operation not permitted
Current system time: 2025-3-9 18:31:37

最终 main.cpp 代码如下:

#include <pwd.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <iostream>
const cap_value_t cap_list[] = {CAP_SYS_TIME};
const unsigned int cap_list_size = sizeof(cap_list) / sizeof(cap_list[0]);
// Function to set the system time
void SetSystemTime(const struct tm& newTime) {
 struct timeval tv;
 tv.tv_sec = mktime(const_cast<struct tm*>(&newTime)); // Convert struct tm to time_t
 tv.tv_usec = 0;
 if (settimeofday(&tv, nullptr) != 0) {
 perror("Failed to set system time");
 } else {
 std::cout << "System time successfully updated." << std::endl;
 }
}
void GetSystemTime() {
 time_t now = time(nullptr);
 struct tm* currentTime = localtime(&now);
 std::cout << "Current system time: "
 << (currentTime->tm_year + 1900) << "-"
 << (currentTime->tm_mon + 1) << "-"
 << currentTime->tm_mday << " "
 << currentTime->tm_hour << ":"
 << currentTime->tm_min << ":"
 << currentTime->tm_sec << std::endl;
}
void SetChildCapabilities() {
 cap_t caps;
 // Initialize the capability set
 caps = cap_get_proc();
 if (cap_set_flag(caps, CAP_INHERITABLE, cap_list_size, cap_list, CAP_SET) == -1 ||
 cap_set_flag(caps, CAP_PERMITTED, cap_list_size, cap_list, CAP_SET) == -1 ||
 cap_set_flag(caps, CAP_EFFECTIVE, cap_list_size, cap_list, CAP_SET) == -1) {
 std::cerr << "Failed to set capabilities.\n";
 exit(EXIT_FAILURE);
 }
 if (cap_set_proc(caps) == -1) {
 std::cerr << "Failed to apply capabilities.\n";
 exit(EXIT_FAILURE);
 }
 cap_free(caps);
}
// Function to preserve capabilities across exec
void PreserveCapabilities() {
 if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
 perror("Failed to set PR_SET_KEEPCAPS");
 } else {
 std::cout << "Capabilities will be preserved across exec." << std::endl;
 }
}
void SetParentInhCapabilities() {
 cap_t caps;
 // Initialize the capability set
 caps = cap_get_proc();
 if (cap_set_flag(caps, CAP_INHERITABLE, cap_list_size, cap_list, CAP_SET) == -1) {
 std::cerr << "Failed to set capabilities.\n";
 exit(EXIT_FAILURE);
 }
 if (cap_set_proc(caps) == -1) {
 std::cerr << "Failed to apply capabilities.\n";
 exit(EXIT_FAILURE);
 }
 cap_free(caps);
}
void ChangeUid(uid_t new_uid) {
 if (setuid(new_uid) == -1) {
 std::cerr << "Failed to change UID.\n";
 exit(EXIT_FAILURE);
 }
}
void PrintCapabilities() {
 cap_t caps = cap_get_proc();
 if (caps == nullptr) {
 std::cerr << "Failed to get capabilities.\n";
 exit(EXIT_FAILURE);
 }
 char* cap_text = cap_to_text(caps, nullptr);
 if (cap_text == nullptr) {
 std::cerr << "Failed to convert capabilities to text.\n";
 cap_free(caps);
 exit(EXIT_FAILURE);
 }
 std::cout << "Process capabilities: " << cap_text << std::endl;
 cap_free(caps);
 cap_free(cap_text);
}
int main() {
 // Set capabilities for the parent process
 pid_t pid = fork();
 if (pid == -1) {
 std::cerr << "Failed to fork process.\n";
 return EXIT_FAILURE;
 } else if (pid == 0) {
 // Child process
 std::cout << "Child process PID: " << getpid() << std::endl;
 PreserveCapabilities();
 SetParentInhCapabilities();
 // Change UID for the child process
 struct passwd* pw = getpwnam("nobody");
 if (pw == nullptr) {
 std::cerr << "Failed to get UID for 'nobody'.\n";
 return EXIT_FAILURE;
 }
 ChangeUid(pw->pw_uid);
 // Set capabilities of the child process
 SetChildCapabilities();
 // Print capabilities in the child process
 PrintCapabilities();
 // Child process code here
 struct tm newTime = {};
 // Set your desired time here
 newTime.tm_year = 2025 - 1900; // Year since 1900
 newTime.tm_mon = 2 - 1; // Month (0-11)
 newTime.tm_mday = 9; // Day of the month
 newTime.tm_hour = 10; // Hour (0-23)
 newTime.tm_min = 30; // Minute (0-59)
 newTime.tm_sec = 0; // Second (0-59)
 // Set the new system time
 SetSystemTime(newTime);
 GetSystemTime();
 execl("/bin/sh", "sh", "-c", "./setTime.bin", NULL);
 } else {
 // Parent process
 std::cout << "Parent process PID: " << getpid() << std::endl;
 // Print capabilities in the parent process
 PrintCapabilities();
 wait(NULL); // Wait for the child process to finish
 }
 return EXIT_SUCCESS;
}

最终 setTime.cpp 的代码如下

#include <sys/capability.h>
#include <sys/time.h>
#include <unistd.h>
#include <ctime>
#include <iostream>
// Function to set the system time
void SetSystemTime(const struct tm& newTime) {
 struct timeval tv;
 tv.tv_sec = mktime(const_cast<struct tm*>(&newTime)); // Convert struct tm to time_t
 tv.tv_usec = 0;
 if (settimeofday(&tv, nullptr) != 0) {
 perror("Failed to set system time");
 } else {
 std::cout << "System time successfully updated." << std::endl;
 }
}
void GetSystemTime() {
 time_t now = time(nullptr);
 struct tm* currentTime = localtime(&now);
 std::cout << "Current system time: "
 << (currentTime->tm_year + 1900) << "-"
 << (currentTime->tm_mon + 1) << "-"
 << currentTime->tm_mday << " "
 << currentTime->tm_hour << ":"
 << currentTime->tm_min << ":"
 << currentTime->tm_sec << std::endl;
}
void PrintCapabilities() {
 cap_t caps = cap_get_proc();
 if (caps == nullptr) {
 std::cerr << "Failed to get capabilities.\n";
 exit(EXIT_FAILURE);
 }
 char* cap_text = cap_to_text(caps, nullptr);
 if (cap_text == nullptr) {
 std::cerr << "Failed to convert capabilities to text.\n";
 cap_free(caps);
 exit(EXIT_FAILURE);
 }
 std::cout << "Process capabilities: " << cap_text << std::endl;
 cap_free(caps);
 cap_free(cap_text);
}
int main() {
 std::cout << "setTime.bin:" << std::endl;
 cap_t caps;
 caps = cap_get_proc();
 PrintCapabilities();
 struct tm newTime = {};
 // Set your desired time here
 newTime.tm_year = 2025 - 1900; // Year since 1900
 newTime.tm_mon = 3 - 1; // Month (0-11)
 newTime.tm_mday = 9; // Day of the month
 newTime.tm_hour = 10; // Hour (0-23)
 newTime.tm_min = 30; // Minute (0-59)
 newTime.tm_sec = 0; // Second (0-59)
 SetSystemTime(newTime);
 GetSystemTime();
 sleep(999);
 return 0;
}

setTime.cpp 编译命令为 g++ ./setTime.cpp -std=c++11 -lcap -o ./setTime.bin

最终执行成功,结果如下,第一个 Process capabilities 输出的是root的所有权限,第二个是输出的切换用户后的子进程的权限,倒数第三行是setTime.bin的权限输出。输出显示两次设置时间都成功,一次是调用execl前,一次是execl调用setTime.bin。

[mario@vbox CPP]$ g++ ./main.cpp -std=c++11 -lcap && sudo ./a.out
Parent process PID: 12278
Child process PID: 12279
Process capabilities: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,35,36+ep
Capabilities will be preserved across exec.
Process capabilities: = cap_sys_time+eip cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,35,36+p
System time successfully updated.
Current system time: 2025-2-9 10:30:0
setTime.bin:
Process capabilities: = cap_sys_time+eip
System time successfully updated.
Current system time: 2025-3-9 10:30:0

找不到动态库

至此,cap是设定好了,但我们很快会碰到新问题,设置了setcap的程序是无法使用LD_LIBRARY_PATH 的,因为系统认为这不安全。这就导致程序找不到我们在那个变量里设置的动态库。所以我们还需要在 /etc/ld.so.conf.d 文件夹中添加动态库的路径,然后运行sudo ldconfig更新缓存。这个文件夹下的文件内容就是一行行的路径,如下所示:

[mario@vbox ld.so.conf.d]$ cd /etc/ld.so.conf.d/
[mario@vbox ld.so.conf.d]$ ls
dyninst-x86_64.conf libiscsi-x86_64.conf
kernel-3.10.0-957.el7.x86_64.conf mariadb-x86_64.conf
[mario@vbox ld.so.conf.d]$ cat dyninst-x86_64.conf 
/usr/lib64/dyninst

更多办法来自deepseek:

方法 1:使用 rpath 硬编码库路径

将库路径直接嵌入可执行文件,绕过对 LD_LIBRARY_PATH 的依赖。

  1. 编译时指定 rpath

    gcc -Wl,-rpath=/path/to/your/libs -o your_program your_source.c
    
    • -Wl,-rpath 会将库路径写入可执行文件的元数据。
  2. 修改已有二进制文件(需 patchelf 工具)

    # 安装 patchelf(Debian/Ubuntu)
    sudo apt install patchelf
    # 为程序设置 rpath
    sudo patchelf --set-rpath '/path/to/libs' /path/to/program
    # 重新赋予能力(patchelf 会清除能力)
    sudo setcap 'cap_net_bind_service=+ep' /path/to/program
    

方法 2:将库路径加入系统配置

将库目录添加到系统信任的路径列表中。

  1. 创建配置文件:

    echo '/path/to/libs' | sudo tee /etc/ld.so.conf.d/myapp.conf
    
  2. 更新动态库缓存:

    sudo ldconfig
    

方法 3:将库文件复制到标准目录

将动态库复制到系统默认搜索路径(如 /usr/lib/lib):

sudo cp /path/to/libs/*.so* /usr/lib/
sudo ldconfig
作者:mariocanfly原文地址:https://www.cnblogs.com/mariocanfly/p/18761174

%s 个评论

要回复文章请先登录注册