折腾半天,又是问deepseek又是问朋友,终于解决了。
Linux 有一个能力(capability)机制,相当于是对root权限的细分,你可以把这些权限细分给进程或程序。能力的介绍可以看看这个博客,我就不多说了。
出于安全考虑,我需要给子进程设置能力,同时又要切换到普通用户,再用execl执行别的程序。结果搞半天都没搞定。
关键的地方在于,execl调用可执行文件后的能力,既取决于进程本身,也取决于可执行文件的能力设置。我进行了一些测试,终于是搞定了。以下是一个调用子进程后通过execl执行设定系统时间的程序的例子。设定系统时间需要root权限,或者 CAP_SYS_TIME
能力。测试的操作系统为 CentOS 7.6
首先,我们执行timedatectl set-ntp false
关闭系统自动校时。
然后,写一个设定时间、打印时间然后睡觉的C++程序。
#include
#include
#include
#include
// Function to set the system time
void SetSystemTime(const struct tm& newTime) {
struct timeval tv;
tv.tv_sec = mktime(const_cast(&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 tm_year + 1900) tm_mon + 1) tm_mday tm_hour tm_min tm_sec
编译:
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Function to set the system time
void SetSystemTime(const struct tm& newTime) {
struct timeval tv;
tv.tv_sec = mktime(const_cast(&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 tm_year + 1900) tm_mon + 1) tm_mday tm_hour tm_min tm_sec 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
确认你的系统中有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拉起别的程序,然后再查询的,结果查询出来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
#include
#include
#include
#include
#include
#include
#include
#include
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(&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 tm_year + 1900) tm_mon + 1) tm_mday tm_hour tm_min tm_sec 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
最终 setTime.cpp 的代码如下
#include
#include
#include
#include
#include
// Function to set the system time
void SetSystemTime(const struct tm& newTime) {
struct timeval tv;
tv.tv_sec = mktime(const_cast(&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 tm_year + 1900) tm_mon + 1) tm_mday tm_hour tm_min tm_sec
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:
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
将库目录添加到系统信任的路径列表中。
创建配置文件:
echo '/path/to/libs' | sudo tee /etc/ld.so.conf.d/myapp.conf
更新动态库缓存:
sudo ldconfig
将动态库复制到系统默认搜索路径(如 /usr/lib
或 /lib
):
sudo cp /path/to/libs/*.so* /usr/lib/
sudo ldconfig
本文来自博客园,作者:mariocanfly,转载请注明原文链接:https://www.cnblogs.com/mariocanfly/p/18761174
参与评论
手机查看
返回顶部