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
的依赖。
编译时指定
rpath
:gcc -Wl,-rpath=/path/to/your/libs -o your_program your_source.c
-Wl,-rpath
会将库路径写入可执行文件的元数据。
修改已有二进制文件(需
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:将库路径加入系统配置
将库目录添加到系统信任的路径列表中。
创建配置文件:
echo '/path/to/libs' | sudo tee /etc/ld.so.conf.d/myapp.conf
更新动态库缓存:
sudo ldconfig
方法 3:将库文件复制到标准目录
将动态库复制到系统默认搜索路径(如 /usr/lib
或 /lib
):
sudo cp /path/to/libs/*.so* /usr/lib/
sudo ldconfig