34

我正在编写一些软件(在 C++ 中,用于 Linux/Mac OSX),它以非特权用户身份运行,但在某些时候需要 root 特权(以创建新的虚拟设备)。

以 root 身份运行这个程序不是一个选项(主要是为了安全问题),我需要知道“真实”用户的身份(uid)。

有没有办法模仿“sudo”命令行为(询问用户密码)来临时获得 root 权限并执行特定任务?如果是这样,我会使用哪些功能?

非常感谢您的帮助 !

4

7 回答 7

20

如果您每次都需要 root 权限,最好的办法是以 root 身份启动程序并使用setuidsetgid删除它们(在子进程中) 。这就是 apache 在需要绑定到受限端口 80 时所做的事情。

如果获得root权限是例外而不是规则并且程序交互运行,另一种方法是编写程序add_interface并执行

sudo add_interface args

并让 sudo 为您处理身份验证。您可能想要使用图形前端而不是 sudo,例如 gksu、gksudo、kdesu 或 kdesudo。我不会尝试自己实现安全密码输入;这可能是一个棘手的问题,您可能会留下巨大的安全漏洞和功能问题(您支持指纹读取器吗?)。

另一种选择是polkit,以前称为 PolicyKit。

于 2010-03-20T16:21:42.770 回答
15

原始答案

您可能会考虑可执行文件本身的 setuid 开关。Wikipedia 上有一篇文章甚至可以非常有效地向您展示两者之间的区别geteuid()getuid()前者用于找出您“模仿”谁,而后者用于找出您“是谁”。sudo 进程,例如,geteuid 应该返回 0(root)并 getuid 您的用户 id,但是,它的子进程确实以 root 身份运行(您可以使用 验证这一点sudo id -u -r)。

我认为没有一种方法可以轻松地以编程方式获得 root 访问权限——毕竟,应用最小权限原则,你为什么需要这样做?常见的做法是仅以提升的权限运行代码的有限部分。许多守护进程等也被设置在现代系统下,以他们自己的用户身份运行,拥有他们需要的大部分权限。只有非常特定的操作(挂载等)才真正需要 root 权限。

2013 年更新

我最初的答案是正确的(尽管我 2013 年的自己可能比 2010 年的自己做得更好),但是如果您正在设计一个需要 root 访问权限的应用程序,您可能需要确切考虑需要哪种 root 访问权限并考虑使用POSIX 功能 (手册页)。这些与L4 等人实施的基于能力的安全性不同。POSIX 功能允许您的应用程序被授予 root 权限的子集。例如CAP_SYS_MODULE,将允许您插入内核模块,但不授予您其他根权限。这在发行版中使用,例如Fedora 具有完全删除具有不加选择的 root 访问权限的 setuid 二进制文件的功能。

这很重要,因为作为程序员,您的代码显然是完美的!但是,您所依赖的库(叹息,如果您编写了它们就好了!)可能存在漏洞。使用功能,您可以限制此漏洞的使用,并使您和您的公司免受与安全相关的审查。这让每个人都更快乐。

于 2010-03-20T16:25:18.560 回答
9

您无法获得 root 权限,您必须从它们开始并根据需要降低您的权限。执行此操作的常用方法是安装设置了“setuid”位的程序:这将使用文件所有者的有效用户 ID 运行程序。如果你在ls -l上运行sudo,你会看到它是这样安装的:

-rwsr-xr-x 2 root root 123504 2010-02-25 18:22 /usr/bin/sudo

当您的程序以 root 权限运行时,您可以调用setuid(2)系统调用将您的有效用户 ID 更改为某个非特权用户。我相信(但尚未尝试过)您可以在 setuid 位打开的情况下以 root 身份安装您的程序,立即降低权限,然后根据需要恢复权限(但是,一旦您降低权限,您可能不会能够恢复它)。

更好的解决方案是拆分需要以 root 身份运行的程序部分,并在打开 setuid 位的情况下安装它。当然,您需要采取合理的预防措施,使其不能在您的主程序之外调用。

于 2010-03-20T16:26:53.860 回答
4

通常这是通过制作二进制 suid-root 来完成的。

管理这种情况以便很难对您的程序进行攻击的一种方法是最小化以 root 身份运行的代码,如下所示:

int privileged_server(int argc, char **argv);
int unprivileged_client(int argc, char **argv, int comlink);


int main(int argc, char **argv) {
    int sockets[2];
    pid_t child;
    socketpair(AF_INET, SOCK_STREAM, 0);  /* or is it AF_UNIX? */

    child = fork();
    if (child < 0) {
        perror("fork");
        exit(3);
    } elseif (child == 0) {
        close(sockets[0]);
        dup2(sockets[1], 0);
        close(sockets[1]);
        dup2(0, 1);
        dup2(0, 2); /* or not */
        _exit(privileged_server(argc, argv));
    } else {
        close(sockets[1]);
        int rtn;
        setuid(getuid());
        rtn = unprivileged_client(argc, argv, sockets[0]);
        wait(child);
        return rtn;
    }
}

现在,非特权代码通过 fd comlink(这是一个连接的套接字)与特权代码对话。相应的特权代码使用 stdin/stdout 作为其 comlink 的结尾。

特权代码需要验证它需要执行的每个操作的安全性,但由于与非特权代码相比,此代码很小,这应该相当容易。

于 2010-03-20T16:29:44.430 回答
2

您可能想看看这些 API:

setuid, seteuid, setgid, setegid, ...

它们是<unistd.h>在 Linux 系统的标头中定义的(对 MAC 了解不多,但那里也应该有类似的标头)。

我可以看到的一个问题是该进程必须具有足够的权限才能更改其用户/组 ID。否则调用上述函数将导致errorno设置为的错误EPERM

我建议您以root用户身份运行程序,一开始就将有效用户 ID(使用seteuid)更改为低权限用户。然后,每当您需要提升权限时,提示输入密码,然后seteuid再次使用以恢复给root用户。

于 2010-03-20T16:26:54.970 回答
2

在 OS X 上,您可以使用该AuthorizationExecuteWithPrivileges功能。授权服务任务页面对此(及相关)功能进行了详细讨论。

下面是一段 C++ 代码,用于以管理员权限执行程序:

static bool execute(const std::string &program, const std::vector<std::string> &arguments)
{
    AuthorizationRef ref;
    if (AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &ref) != errAuthorizationSuccess) {
        return false;
    }

    AuthorizationItem item = {
        kAuthorizationRightExecute, 0, 0, 0
    };
    AuthorizationRights rights = { 1, &item };
    const AuthorizationFlags flags = kAuthorizationFlagDefaults
                                   | kAuthorizationFlagInteractionAllowed
                                   | kAuthorizationFlagPreAuthorize
                                   | kAuthorizationFlagExtendRights;

    if (AuthorizationCopyRights(ref, &rights, kAuthorizationEmptyEnvironment, flags, 0) != errAuthorizationSuccess) {
        AuthorizationFree(ref, kAuthorizationFlagDestroyRights);
        return false;
    }

    std::vector<char*> args;
    for (std::vector<std::string>::const_iterator it = arguments.begin(); it != arguments.end(); ++it) {
        args.push_back(it->c_str());
    }
    args.push_back(0);

    OSStatus status = AuthorizationExecuteWithPrivileges(ref, program.c_str(), kAuthorizationFlagDefaults, &args[0], 0);

    AuthorizationFree(ref, kAuthorizationFlagDestroyRights);
    return status == errAuthorizationSuccess;
}
于 2016-02-10T13:26:05.507 回答
1

您可以尝试通过后台 shell 启动命令来创建虚拟设备(包括 sudo)。在您自己的对话框中询问用户密码,并在 sudo 要求时将其输入 shell。还有其他解决方案,例如使用 gksu,但不能保证在每台机器上都可用。

您不会以 root 身份运行整个程序,而只是其中需要 root 的一小部分。您应该为此生成一个单独的进程,并且 sudo 可能会对您有所帮助。

于 2010-03-20T16:16:18.847 回答