Erlo

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

2025-03-09 21:29:02 发布   6 浏览  
页面报错/反馈
收藏 点赞

折腾半天,又是问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 调用

一直到这里,整个功能实现看上去都很简单,但是为什么之前我调不通呢?真是蛋疼,之前我都是在子进程里用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:

方法 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

登录查看全部

参与评论

评论留言

还没有评论留言,赶紧来抢楼吧~~

手机查看

返回顶部

给这篇文章打个标签吧~

棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认