【问题标题】:Dropping root privileges删除 root 权限
【发布时间】:2011-03-22 10:14:27
【问题描述】:

我有一个以 root 身份启动的守护进程(因此它可以绑定到低端口)。初始化后,出于安全原因,我非常希望它放弃 root 权限。

谁能指点我一个已知正确的 C 代码可以做到这一点?

我阅读了手册页,查看了不同应用程序中的各种实现,它们都是不同的,其中一些非常复杂。这是与安全相关的代码,我真的不想重新发明其他人所犯的相同错误。我正在寻找的是一个最佳实践,已知的良好,可移植的库函数,我可以使用它,因为它会得到正确的知识。有这种事吗?

供参考:我以 root 身份开始;我需要更改为在不同的 uid 和 gid 下运行;我需要正确设置补充组;之后我不需要改回root权限。

【问题讨论】:

  • 这在 unix 之间变化很大——有什么特别的吗?如果你需要一个“便携”的解决方案,它会很乱,你最好抓住例如OpenSSH 中的 Permanent_set_uid() 函数 - 在 uidswap.c 文件中
  • 也许是一个更好的策略,不需要 root 所以它不需要被删除。另见Allow non-root process to bind to port 80 and 443? 相关主题是Setuid Demystified bye Chen、Dean 和 Wagner。不同风格的 Unix 的行为略有不同。这是另一个让你的非特权进程绑定到端口的原因。
  • 另见 Setuid Demystified 在 Usenix。

标签: c unix root


【解决方案1】:

这是我最擅长的:

#define _GNU_SOURCE  // for secure_getenv()


int drop_root_privileges(void) {  // returns 0 on success and -1 on failure
    gid_t gid;
    uid_t uid;

    // no need to "drop" the privileges that you don't have in the first place!
    if (getuid() != 0) {
        return 0;
    }

    // when your program is invoked with sudo, getuid() will return 0 and you
    // won't be able to drop your privileges
    if ((uid = getuid()) == 0) {
        const char *sudo_uid = secure_getenv("SUDO_UID");
        if (sudo_uid == NULL) {
            printf("environment variable `SUDO_UID` not found\n");
            return -1;
        }
        errno = 0;
        uid = (uid_t) strtoll(sudo_uid, NULL, 10);
        if (errno != 0) {
            perror("under-/over-flow in converting `SUDO_UID` to integer");
            return -1;
        }
    }

    // again, in case your program is invoked using sudo
    if ((gid = getgid()) == 0) {
        const char *sudo_gid = secure_getenv("SUDO_GID");
        if (sudo_gid == NULL) {
            printf("environment variable `SUDO_GID` not found\n");
            return -1;
        }
        errno = 0;
        gid = (gid_t) strtoll(sudo_gid, NULL, 10);
        if (errno != 0) {
            perror("under-/over-flow in converting `SUDO_GID` to integer");
            return -1;
        }
    }

    if (setgid(gid) != 0) {
        perror("setgid");
        return -1;
    }
    if (setuid(uid) != 0) {
        perror("setgid");
        return -1;    
    }

    // change your directory to somewhere else, just in case if you are in a
    // root-owned one (e.g. /root)
    if (chdir("/") != 0) {
        perror("chdir");
        return -1;
    }

    // check if we successfully dropped the root privileges
    if (setuid(0) == 0 || seteuid(0) == 0) {
        printf("could not drop root privileges!\n");
        return -1;
    }

    return 0;
}

【讨论】:

    【解决方案2】:

    您正在寻找这篇文章:

    POS36-C. Observe correct revocation order while relinquishing privileges

    不确定如何在不复制该页面内容的情况下最好地将一些信息放在那里...

    【讨论】:

    【解决方案3】:

    为了删除所有权限(用户和组),您需要在用户之前删除组。假设useridgroupid 包含用户的ID 和您要拖放到的组,并且假设有效ID 也是root,这可以通过调用setuid()setgid() 来完成:

    if (getuid() == 0) {
        /* process is running as root, drop privileges */
        if (setgid(groupid) != 0)
            fatal("setgid: Unable to drop group privileges: %s", strerror(errno));
        if (setuid(userid) != 0)
            fatal("setuid: Unable to drop user privileges: %S", strerror(errno));
    }
    

    如果您偏执,可以尝试恢复您的 root 权限,但应该会失败。如果它没有失败,你就退出:

     if (setuid(0) != -1)
         fatal("ERROR: Managed to regain root privileges?");
    

    另外,如果您仍然偏执,您可能还想seteuid()setegid(),但这不是必需的,因为 setuid() 和 setgid() 已经设置了所有 ID(如果进程是拥有的)按根目录。

    补充组列表有问题,因为没有设置补充组的POSIX函数(有getgroups(),但没有setgroups())。您可以使用 BSD 和 Linux 扩展 setgroups(),这与您有关。

    您还应该chdir("/") 或任何其他目录,这样该进程就不会保留在根拥有的目录中。

    由于您的问题一般是关于 Unix,因此这是非常通用的方法。请注意,在 Linux 中,这不再是首选方法。在当前的 Linux 版本中,您应该在可执行文件上设置CAP_NET_BIND_SERVICE capability,并以普通用户身份运行它。无需 root 访问权限。

    【讨论】:

    • 您还想设置 gid - 这可能会在不同的 unix 上略有不同,因为 setuid/setgid 实际上是否会与真实和保存的 id 混淆
    • 这确实需要一个可移植的解决方案,所以恐怕没有 Linux 功能。我确实尝试过使用 setuid() 和 setgid() 的简单方法;它没有正确设置组(如果你不调用 setgroups(),显然你最终仍然可以成为某些 root 组的成员!)。
    • @nos 谢谢。扩大到覆盖群体。如果该进程由root拥有(如OP所述)或者它是setuid-root,则setuid()和setgid()已经设置了所有ID(真实,有效和保存)。这是在规范中。否则,实现将不符合 POSIX。
    • 你可以使用initgroups(3)重置补充组,它足够便携(至少BSD和GNU libc有)。
    • 始终清除补充组。 initgroups 存在于 AIX、Solaris、HP-UX、FreeBSD、Darwin、Linux 和可能的其他平台上。包括它,以便在找不到时构建失败。不删除所有组可能会导致严重的系统妥协和 CVE。我认为 chdir 根本没有任何安全隐患,是吗?
    猜你喜欢
    • 2011-02-11
    • 2018-03-14
    • 2018-10-03
    • 2013-03-20
    • 1970-01-01
    • 2015-06-22
    • 1970-01-01
    • 2015-10-01
    • 1970-01-01
    相关资源
    最近更新 更多