以root权限调用c++函数,而不以root身份执行整个程序

Calling a C++ function with root privileges, without executing the whole program as root

本文关键字:root 执行 身份 程序 权限 调用 c++ 函数      更新时间:2023-10-16

目前我正在玩蓝牙LE和iBeacon设备。我写了一个服务器,不断寻找附近的信标。

我的服务器遵循以下示例(Link)

不幸调用了函数:

hci_le_set_scan_parameters()

需要root权限。

因为我不想用根权限运行整个服务器,我想问是否有可能只调用这个函数的根权限?

我知道在执行程序时要求sudo总是至少有问题,我找不到任何其他扫描iBeacons的可能性。如果还有其他的可能性,我也很高兴听到。

感谢您的帮助和亲切的问候

nPLus

根据POSIX, UID/GID是进程属性。进程内的所有代码都使用当前为整个进程设置的UID/GID执行。

您可以以root身份启动服务器,然后立即放弃root权限。然后,您可以在执行函数时使用seteuid(2)临时获得根权限。

参见此回答

您也可以只获得选定的capabilities(7)代替(临时或永久)。


线程安全注意

在Linux上,UID/GID是每个线程的属性,可以为单个线程设置它们,参见seteuid()手册页中的NOTES部分和这篇文章。

如果可以将特权部分移动到单独的进程中,我强烈建议这样做。父进程将至少构造一个Unix Domain套接字对,一端为自己保留,另一端作为子进程的标准输入或输出。

使用Unix域套接字对的原因是这样的套接字对不仅是双向的,而且还支持识别另一端的进程,并将打开的文件描述符从一个进程传递到另一个进程。

例如,如果您的主进程需要超级用户访问权限来读取文件,可能在特定目录中,或者在其他可识别的地方,您可以将这些文件的打开移动到单独的辅助程序中。通过使用Unix域套接字对在两者之间进行通信,辅助程序可以使用getsockopt(ufd、SOL_SOCKET、SO_PEERCRED、&ucred、&ucred_size)来获取对等凭证:进程ID、有效用户ID和有效组ID。对伪文件/proc/PID/exe(其中PID是一个正十进制的进程ID)使用readlink(),您可以获得另一端当前正在运行的可执行文件。如果目标文件/设备可以打开,那么助手可以将打开的文件描述符传递回父进程。(Linux中的访问检查仅在打开文件描述符时完成。只有当描述符被打开为只读或套接字读端被关闭时,读访问才会被阻塞,而只有当描述符被打开为只读或套接字写端被关闭时,写访问才会被阻塞。)我建议传递一个int作为数据,如果成功,则传递0作为辅助消息,否则传递errno错误代码(没有辅助数据)。

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

辅助进程可以通过setuid或通过Linux文件系统功能获得特权。例如,只给帮助器CAP_DAC_OVERRIDE功能可以让它绕过文件读、写和执行检查。在Debian衍生版本中,用于操作文件系统功能的命令行工具setcap位于libcap2-bin包中。


如果不能将特权部分移动到单独的进程中,可以使用Linux、bsd和HP-UX系统中支持的接口:setresuid(),它可以在单个调用中设置真实有效保存用户id。(对于真实的、有效的和保存的组id,有一个相应的setresgid()调用,但是当使用那个组id时,记住补充组列表不会被修改;需要显式调用setgroups()或initgroups()来修改补充组列表。也有文件系统用户ID和文件系统组ID,但是C库将设置它们,以便在设置有效用户和/或组ID时匹配有效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信号和一个内部处理程序来通知其他线程切换标识,作为操作这些标识的库函数的一部分。

在您的进程早期,创建一个特权线程,它将保留root(0)作为保存的用户身份,但否则将真实身份复制到有效和保存的身份。对于主要流程,将真实身份复制到有效和保存的身份。当特权线程需要执行某些操作时,它首先将有效用户标识设置为root,然后执行该操作,然后将有效用户标识重置为真实用户标识。通过这种方式,特权部分被限制在这个线程中,并且只在必要时应用于部分,因此大多数常见的信号等漏洞将没有机会工作,除非它们恰好发生在这样一个特权部分。

这样做的缺点是,它是必要的,没有任何身份改变的C库函数(setuid(), seteuid(), setgid(), setegid(), setfsuid(), setfsgid(), setreuid(), setregid(), setregid(), setregid(), setregid(), setregid(), setregid(), setresgid())必须由进程中的任何代码使用。因为在Linux C库函数是弱的,你可以通过用你自己的版本替换它们来确保:你自己定义这些函数,用正确的名字(如所示和两个下划线)和参数。


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