看一下samba可以做到这一点的一个例子。samba 守护进程以 root 身份运行,但会尽快分叉并假定普通用户的凭据。
Unix 系统有两组独立的凭据:真实用户/组 ID 和有效用户/组 ID。真正的集合确定了您的真实身份,而有效的集合定义了您可以访问的内容。如果您是 root 用户,您可以随意更改有效的 uid/gid — 包括普通用户并再次返回 — 因为您的真实用户/组 id 在转换期间保持 root 身份。因此,在单个进程中执行此操作的另一种方法是seteuid/gid
根据需要来回应用不同用户的权限。如果您的服务器守护程序以 root 身份运行或以 root 身份运行,CAP_SETUID
那么这将被允许。
但是,请注意,如果您有能力随心所欲地切换有效的 uid/gid 并且您的应用程序被颠覆,那么该颠覆可以例如将有效的 uid/gid 切换回 0,您可能会遇到严重的安全漏洞。这就是为什么要谨慎地尽快永久删除所有权限,包括您的真实用户 uid/gid。
出于这个原因,以 root 身份运行单个侦听套接字是正常且安全的,然后通过调用来分叉并更改真实和有效的用户 ID setuid
。然后就不能变回来了。您的分叉进程将拥有被accept()
编辑的套接字,因为它是一个分叉。每个进程只是关闭它们不需要的文件描述符;套接字保持活动状态,因为它们被相反进程中的文件描述符引用。
您也可以尝试通过自己单独检查权限来强制执行权限,但我希望很明显,这可能容易出错,有很多边缘情况并且更有可能出错(例如,它不适用于 POSIX ACL除非您也专门实施)。
所以,你有三个选择:
- 分叉和
setgid()
/setuid()
到你想要的用户。如果需要通信,请在 fork 之前使用pipe(2)
or 。socketpair(2)
- 不要根据需要分叉和
seteuid()
/setegid()
周围(不太安全:更有可能意外破坏您的服务器)。
- 不要乱用系统凭据;手动执行权限(不太安全:更有可能授权错误)。
如果您需要与守护程序通信,那么尽管通过套接字或管道进行通信可能更困难,但第一个选项确实是正确的安全方式。例如,查看ssh 如何进行权限分离。您可能还考虑是否可以更改您的体系结构,以便进程可以共享一些内存或磁盘空间,而不是任何通信。
您提到您考虑为每个用户运行一个单独的进程,但需要一个侦听 TCP 端口。你仍然可以这样做。只需让一个主守护进程监听 TCP 端口并将请求分派给每个用户守护进程并根据需要进行通信(例如,通过 Unix 域套接字)。这实际上与拥有一个分叉主守护进程几乎相同;我认为后者会更容易实现。
进一步阅读:credentials(7)
手册页。另请注意,Linux 具有文件系统 uid/gids;除了发送信号等其他内容外,这与有效的 uid/gids 几乎相同。如果您的用户没有 shell 访问权限并且无法运行任意代码,那么您无需担心差异。