我正在编写一些软件(在C ++中,用于Linux / Mac OSX),它作为非特权用户运行,但在某些时候需要root权限(以创建新的虚拟设备)。
以root身份运行此程序不是一个选项(主要用于安全问题),我需要知道“真实”用户的身份(uid)。
有没有办法模仿“sudo”命令行为(请求用户密码)暂时获得root权限并执行特定任务?如果是这样,我会使用哪些功能?
非常感谢你的帮助!
答案 0 :(得分:20)
如果您每次都需要root权限,最好的方法是以root身份启动您的程序,并使用setuid和setgid删除它们(在子流程中)。这就是apache在需要绑定到受限端口80时所做的事情。
如果获取root权限是异常而不是规则并且程序以交互方式运行,另一种方法是编写程序add_interface并执行
sudo add_interface args
让sudo为您处理身份验证。您可能希望使用像gksu,gksudo,kdesu或kdesudo这样的图形前端,而不是sudo。我不会尝试自己实现安全密码输入;它可能是一个棘手的问题,你可能会留下大量安全漏洞和功能问题(你支持指纹识别器吗?)。
另一种选择是polkit,以前称为PolicyKit。
答案 1 :(得分:15)
原始回答
您可能会考虑可执行文件本身的setuid开关。维基百科上有一个article,甚至可以非常有效地向您展示geteuid()
和getuid()
之间的区别,前者是为了找出你“模仿”的人而后者是为了你是谁“是”。 sudo进程,例如,geteuid应返回0(root)并getuid用户的id,但是,其子进程确实以root身份运行(您可以使用sudo id -u -r
验证这一点。)
我认为有一种方法可以轻松地以编程方式获得root访问权限 - 毕竟,应用最小权限原则,您为什么需要?通常的做法是仅使用提升的权限运行有限的代码部分。许多守护进程等也在现代系统下设置为以他们自己的用户身份运行,具有他们所需的大部分权限。只有非常具体的操作(安装等)才能真正需要root权限。
2013年更新
我的原始答案代表(虽然我的2013年自己可能会比我的2010年更好)但是如果你正在设计一个需要root访问权限的应用程序,你可能想要考虑到需要什么样的root访问权限并考虑使用POSIX Capabilities (man page)。这些与L4等人实施的capability-based security不同。 POSIX功能允许您的应用程序被授予root权限的子集。例如,CAP_SYS_MODULE
将允许您插入内核模块,但不会为您提供其他根权限。这在分布中使用,例如Fedora has a feature to completely remove setuid binaries具有不加选择的root权限。
这很重要,因为作为程序员,您的代码显然是完美的!但是,你所依赖的图书馆(感叹,如果只是你写的那些!)可能会有漏洞。使用功能,您可以限制此漏洞的使用,并使您自己和您的公司免受安全相关的审查。这让每个人都更开心。
答案 2 :(得分:7)
您无法获得root权限,您必须从这些权限开始并根据需要减少您的权限。执行此操作的常用方法是使用“setuid”位设置安装程序:这将使用文件所有者的有效用户标识运行程序。如果您在ls -l
上运行sudo
,您会看到它是以这种方式安装的:
-rwsr-xr-x 2 root root 123504 2010-02-25 18:22 /usr/bin/sudo
当您的程序以root权限运行时,您可以调用setuid(2)
系统调用将您的有效用户ID更改为某些非特权用户。我相信(但是没有尝试过这个)你可以在root用户的基础上安装你的程序,启用setuid位,立即减少权限,然后根据需要恢复权限(但是,一旦你降低权限,你可能不会能够恢复它。)
更好的解决方案是打破需要以root用户身份运行的程序,并在打开setuid位的情况下安装它。当然,您需要采取合理的预防措施,不能在主程序之外调用它。
答案 3 :(得分: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的结尾。
特权代码需要验证它需要执行的每个操作的安全性,但由于此代码与非特权代码相比较小,因此这应该相当容易。
答案 4 :(得分:2)
您可能想看一下这些API:
setuid, seteuid, setgid, setegid, ...
它们在Linux系统的标题<unistd.h>
中定义(对MAC不太了解,但你也应该有类似的标题)。
我可以看到的一个问题是,该进程必须具有足够的权限才能更改其用户/组ID。否则,调用上述函数会导致errorno
设置为EPERM
时出错。
我建议您以root
用户身份运行程序,在最开始时将有效用户ID(使用seteuid
)更改为弱势用户。然后,每当您需要提升权限时,请提示输入密码,然后再次使用seteuid
恢复为root
用户。
答案 5 :(得分:2)
在OS X上,您可以使用AuthorizationExecuteWithPrivileges
功能。 Authorization Services Tasks上的页面对此(及相关)函数进行了详细讨论。
这里有一些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;
}
答案 6 :(得分:0)
您可以尝试启动命令以通过后台shell创建虚拟设备(包括sudo)。在您自己的对话框中询问用户密码,并在sudo请求时将其输入shell。还有其他解决方案,比如使用gksu,但不保证每台机器都可以使用。
您不是以root用户身份运行整个程序,而只是以root用户身份运行它的一小部分。您应该为此生成一个单独的进程,sudo可能对您有所帮助。