58

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

任何人都可以指出我知道的 C 中正确的一段代码可以做到这一点吗?

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

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

4

3 回答 3

48

为了删除所有权限(用户和组),您需要在用户之前删除组。鉴于useridandgroupid包含用户的 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(),但这不是必需的,因为如果进程由 root 拥有,setuid() 和 setgid() 已经设置了所有 ID。

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

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

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

于 2010-07-28T21:53:47.610 回答
20

您正在寻找这篇文章:

POS36-C。在放弃特权的同时遵守正确的撤销令

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

于 2015-03-18T10:05:01.987 回答
2

这是我能做的最好的:

#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;
}
于 2017-06-20T16:30:27.343 回答