使用root权限调用C++函数,而不必将整个程序以root身份运行

3

我目前正在尝试使用蓝牙低功耗和iBeacon设备。 我编写了一个服务器来不断寻找附近的信标。

我的服务器遵循这个示例(链接

不幸的是,调用以下函数:

hci_le_set_scan_parameters()

需要root权限。
由于我不想用root权限运行整个服务器,我想问一下是否有可能仅以root权限调用此函数?
我知道在执行程序时要求sudo始终至少是可疑的,但我找不到其他扫描iBeacon的可能性。如果还有其他可能性,我也很乐意听取建议。
感谢您的帮助和祝福。
nPLus

你确定需要root权限吗?也许你可以改变权限或在适当的设备上设置正确的组ID?如果您运行strace <your command>会发生什么?您是否看到任何带有“Permission denied”注释的行? - Manne Tallmarken
2个回答

3
根据POSIX标准,UID/GID是进程属性。您进程中的所有代码都使用当前为整个进程设置的UID/GID执行。
您可以作为root启动服务器,然后立即放弃root权限。接着,在执行函数时,您可以使用seteuid(2)临时获得root权限。
另请参见this answer
您也可以仅获取选定的capabilities(7)(临时或永久)。

线程安全注意事项

据我所知,在Linux上,UID/GID是每个线程的属性,并且可以为单个线程设置它们,请参见seteuid()手册页中的NOTES部分和this post


0
如果您可以将特权部分移动到单独的进程中,我强烈建议这样做。父进程将构造至少一个Unix域套接字对,保留其中一个端口供自己使用,并将另一个端口作为子进程的标准输入或输出。
使用Unix域套接字对的原因是这样的一对不仅是双向的,而且还支持识别另一端的进程,并从一个进程传递打开的文件描述符到另一个进程。
例如,如果您的主进程需要超级用户访问权限来读取文件,可能是在特定目录中或以其他方式可识别的文件,您可以将这些文件的打开操作移动到单独的辅助程序中。通过在两者之间使用Unix域套接字对进行通信,辅助程序可以使用getsockopt(ufd, SOL_SOCKET, SO_PEERCRED, &ucred, &ucred_size)获取对等凭据:进程ID、有效用户ID和有效组ID。使用伪文件/proc/PID/exe上的readlink()(其中PID是正十进制数的进程ID)可以获取另一端当前正在运行的可执行文件。
如果目标文件/设备可以打开,则辅助程序可以将打开的文件描述符传回父进程。(在Linux中,访问检查仅在打开文件描述符时执行。如果描述符是只写打开或套接字读端已关闭,则稍后才会阻止读取访问,如果描述符是只读打开或套接字写端已关闭,则仅在写入访问被阻止。)
我建议将一个int作为数据传递,如果成功则为0,并将描述符作为辅助消息传递,否则为errno错误代码(不带辅助数据)。

然而,考虑这些辅助程序可能被滥用的可能方式是很重要的。限制到特定目录,或者可能有一个系统范围的配置文件,指定允许的路径 glob 模式(并不可写入每个人),并使用例如 fnmatch() 来检查传递的路径是否已列出,是好的方法。

辅助进程可以通过设置 setuid 或通过 Linux 文件系统 capabilities 获得特权。例如,只给予辅助程序 CAP_DAC_OVERRIDE 能力将使其绕过文件读取、写入和执行检查。在 Debian 派生版中,操作文件系统能力的命令行工具 setcap 在 libcap2-bin 包中。


如果您无法将特权部分移动到单独的进程中,可以使用Linux、BSD和HP-UX系统支持的接口:setresuid(),它可以在一次调用中设置真实有效保存的用户ID。(有一个相应的setresgid()调用用于设置真实、有效和保存的组ID,但是当使用该调用时,请记住补充组列表不会被修改;您需要显式调用setgroups()initgroups()来修改补充组列表。) 此外,还有文件系统用户ID和文件系统组ID,但是C库将在设置有效用户和/或组ID时将其设置为匹配的值。

如果使用超级用户权限启动进程,则有效用户ID将为零。 如果您首先使用getresuid(&ruid,&euid,&suid)getresgid(&rgid,&egid,&sgid),则可以使用setresgid(rgid,rgid,rgid)确保仅保留真实组标识,并通过调用setresuid(ruid,ruid,0)暂时放弃超级用户权限。 要重新获得超级用户权限,请使用setresuid(0,ruid,0),要永久放弃超级用户权限,请使用setresuid(ruid,ruid,ruid)
这是可行的,因为进程允许在真实、有效和保存的身份之间切换。 有效身份是管理资源访问的身份。

有一种方法可以将特权限制在进程内的一个专用线程中,但这种方法是繁琐且脆弱的,我不建议使用。

为了将特权限制在单个线程内,您需要创建自定义包装器来包装 SYS_setresuid/SYS_setresuid32SYS_setresgid/SYS_setresgid32SYS_getresuid/SYS_getresuid32SYS_getresgid/SYS_getresgid32SYS_setfsuid/SYS_setfsuid32SYS_setfsgid/SYS_setfsgid32 系统调用。(使包装器调用32位版本,如果返回 -ENOSYS,则回退到16位版本。)

在Linux中,用户和组身份实际上是基于线程而不是进程的。所使用的标准C库将使用实时POSIX信号和内部处理程序来通知其他线程切换身份,作为操作这些身份的库函数的一部分。

在您的进程早期,创建一个特权线程,它将保持根(0)作为保存的用户标识,但是将真实标识复制到有效和保存的标识中。对于主进程,将真实标识复制到有效和保存的标识中。当特权线程需要执行某些操作时,它首先将有效用户标识设置为root,执行操作,然后将有效用户标识重置为真实用户标识。这样,特权部分仅限于此一个线程,并且仅在必要时应用于该部分,因此大多数常见的信号等攻击将没有机会生效,除非它们恰好发生在这样的特权部分。
缺点是,必须确保进程内的任何代码都不使用更改身份的C库函数(setuid()、seteuid()、setgid()、setegid()、setfsuid()、setfsgid()、setreuid()、setregid()、setresuid()、setresgid())。因为在Linux C库函数是弱的,您可以通过替换它们为自己的版本来确保:自己定义这些函数,使用正确的名称(如所示和带有两个下划线)和参数。

在所有不同的方法中,我相信通过 Unix 域套接字对进行身份验证的独立进程是最明智的选择。它最容易使系统健壮,并且至少可以在 POSIX 和 BSD 系统之间移植。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接