4

我的自定义程序以非 root 权限执行,用户 ID 为:uid: 1000 euid: 0其中在 fork() 之后,在子进程中调用 execv() 以运行 SSH 客户端服务。由于我在非特权用户下启动程序,当尝试将套接字绑定到设备时,Linux 内核会执行所有权限检查,导致子程序sock_setbindtodevice()在检查CAP_NET_RAW功能时失败,如下所示。

我当时考虑的解决方案是先在子进程中获取root权限,进行设置所需能力等特权操作,然后回退到非root。

这里的一个问题是,需要下拉到非 root 用户,因为当执行 ssh 命令时,我希望生成的DSA/RSA密钥存储在$HOME/.ssh/known_hosts而不是root/.ssh/known_hosts.

请在下面找到代码片段:

void
global_exec_func (const char *proc_name, const char *proc_path, char **arg_list)
{
    pid_t   pid;
    int     status, euid;

    struct __user_cap_header_struct cap_header_data;
    cap_user_header_t cap_header = &cap_header_data;
    struct __user_cap_data_struct cap_data_data;
    cap_user_data_t cap_data = &cap_data_data;

    pid = fork();
    if (pid < 0) {
       printf("%% can't fork process %s", proc_name);
       return;
    }   

    /*  
     * Child process.
     */
    if (pid == 0) {
         euid = geteuid();          /* Storing euid */

         /*Gaining root privileges */
         if (setuid(0) < 0) {       
             printf("setuid(0) failed");
         }
         printf("After setting: getuid: %d geteuid: %d\n", getuid(),
                  geteuid());

         cap_header->pid = 0;
         cap_header->version = _LINUX_CAPABILITY_VERSION;

         /* Get the capabilities */
         if(capget(cap_header, cap_data)  < 0) {
             printf("failed capget error:%s", strerror(errno));
         }
         cap_data->effective = (1 << CAP_NET_RAW);    /* Set bit 13 */
         cap_data->inheritable = 0;

         /* Set the capabilities */
         if (capset(cap_header, cap_data) < 0) {
             printf("failed capset error:%s", strerror(errno));
         }

         /* Drop back privileges */
         if (seteuid(euid) < 0) {    
             printf("seteuid(euid) failed");
         }
         printf("After drop: getuid: %d geteuid: %d\n", getuid(), geteuid());

         prctl(PR_SET_KEEPCAPS, 1);
         execv(proc_path, arg_list);
         exit(1);
     }
     /*
      * Parent Process code follows
      */


Result:  
 [local]linux#ssh 101.1.1.101
 After setting: getuid: 0 geteuid: 0
 After drop: getuid: 0 geteuid: 0
 The authenticity of host '101.1.1.101 (101.1.1.101)' can't be established.
 DSA key fingerprint is 0c:61:df:01:93:74:1f:5f:49:34:f4:4e:06:e8:d7:5f.
 Are you sure you want to continue connecting (yes/no)? ^C
 [local]linux#

结果显示 ssh 成功,但此时程序以 a 方式运行root,这是不正确的。如何返回 UID,uid: 1000 euid: 0以便将 ssh 密钥存储在正确的目录中。

请对我的解决方案发表评论和建议,它真的解决了问题吗?

4

1 回答 1

5

如果您的程序使用有效的用户 ID root 执行,那么您确实拥有 root 权限。


在 Linux 中,能力分为三组:可继承、允许和有效。Inheritable 定义了哪些功能在exec(). 允许定义进程允许哪些能力。有效定义了当前有效的功能。

编辑添加:当包含将要执行的二进制文件的文件exec()系统确实支持文件系统功能时,这些总是会影响执行进程将具有的功能。请参阅man 7 功能手册页中的功能转换。execve()

将进程的所有者或组从 root 更改为非 root 时,始终清除有效的能力集。默认情况下,允许的能力集也被清除,但prctl(PR_SET_KEEPCAPS, 1L)在身份更改之前调用会告诉内核保持允许的集不变。

因此,要拥有该CAP_NET_RAW功能,您的程序必须在允许集和有效集中拥有它。如果您希望 对CAP_NET_RAW保持有效exec(),则它必须包含在所有三个功能集中。

编辑添加:如果 的目标支持文件功能exec(),则文件功能还必须包含继承和有效集中的那些功能。(仅包括继承和有效集中的能力不会授予能力,因为它不在文件能力的允许集中;但是,如果执行者有能力)。


您可以使用该setcap命令将特定功能授予二进制文件。(现在大多数 Linux 文件系统都支持这些文件功能。)它不需要特权或 setuid。只需记住将所需的功能添加到允许的和有效的集合中。

编辑添加一些示例:

授予(不得为 setuid 或 setgid root)CAP_NET_RAW/usr/bin/myprog

sudo setcap 'cap_net_raw=pe' /usr/bin/myprog

默认情况下,不授予CAP_NET_RAW/usr/bin/myprog但如果执行者具有能力(在可继承和允许的集中),则保留能力(在可继承和允许的集中,并在有效集中激活它):

sudo setcap 'cap_net_raw=ie' /usr/bin/myprog

如果您的程序无论如何都必须是 setuid root,那么您可以使用例如

#define  _GNU_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <sys/capability.h>
#include <sys/prctl.h>

#define   NEED_CAPS 1
static const cap_value_t need_caps[NEED_CAPS] = { CAP_NET_RAW };

int main(void)
{
    uid_t  real = getuid();
    cap_t  caps;

    /* Elevate privileges */
    if (setresuid(0, 0, 0))
        return 1; /* Fatal error, probably not setuid root */

    /* Add need_caps to current capabilities. */
    caps = cap_get_proc();
    if (cap_set_flag(caps, CAP_PERMITTED,   NEED_CAPS, need_caps, CAP_SET) ||
        cap_set_flag(caps, CAP_EFFECTIVE,   NEED_CAPS, need_caps, CAP_SET) ||
        cap_set_flag(caps, CAP_INHERITABLE, NEED_CAPS, need_caps, CAP_SET))
        return 1; /* Fatal error */

    /* Update capabilities */
    if (cap_set_proc(caps))
        return 1; /* Fatal error */

    /* Retain capabilities over an identity change */
    if (prctl(PR_SET_KEEPCAPS, 1L))
        return 1; /* Fatal error */

    /* Return to original, real-user identity */ 
    if (setresuid(real, real, real))
        return 1; /* Fatal error */

    /* Because the identity changed, we need to
     * re-install the effective set. */
    if (cap_set_proc(caps))
        return 1; /* Fatal error */

    /* Capability set is no longer needed. */
    cap_free(caps);

    /* You now have the CAP_NET_RAW capability.
     * It will be retained over fork() and exec().
    */

    return 0;
}
于 2013-07-19T17:31:10.630 回答