8

我有一个将在系统帐户下运行的服务器应用程序,因为在任何给定时间,它将代表系统上的任何用户处理请求。这些请求包含操作文件系统的指令。

这里有一个问题:程序在执行操作时需要牢记该特定用户的权限。例如,joe如果/home/larry其权限为755.

目前我的策略是这样的

  • 获取文件的所有者/组
  • 将其与尝试执行操作的用户的用户 ID/组 ID 进行比较
  • 如果任一匹配(或不匹配),则使用文件中权限字段的适当部分来允许或拒绝该操作

这是明智的吗?有没有更简单的方法来做到这一点?

起初,我正在考虑让应用程序的多个实例在用户帐户下运行 - 但这不是一个选项,因为只有一个实例可以侦听给定的 TCP 端口。

4

3 回答 3

3

看一下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除非您也专门实施)。

所以,你有三个选择:

  1. 分叉和setgid()/setuid()到你想要的用户。如果需要通信,请在 fork 之前使用pipe(2)or 。socketpair(2)
  2. 不要根据需要分叉和seteuid()/setegid()周围(不太安全:更有可能意外破坏您的服务器)。
  3. 不要乱用系统凭据;手动执行权限(不太安全:更有可能授权错误)。

如果您需要与守护程序通信,那么尽管通过套接字或管道进行通信可能更困难,但第一个选项确实是正确的安全方式。例如,查看ssh 如何进行权限分离。您可能还考虑是否可以更改您的体系结构,以便进程可以共享一些内存或磁盘空间,而不是任何通信。

您提到您考虑为每个用户运行一个单独的进程,但需要一个侦听 TCP 端口。你仍然可以这样做。只需让一个主守护进程监听 TCP 端口并将请求分派给每个用户守护进程并根据需要进行通信(例如,通过 Unix 域套接字)。这实际上与拥有一个分叉主守护进程几乎相同;我认为后者会更容易实现。

进一步阅读:credentials(7)手册页。另请注意,Linux 具有文件系统 uid/gids;除了发送信号等其他内容外,这与有效的 uid/gids 几乎相同。如果您的用户没有 shell 访问权限并且无法运行任意代码,那么您无需担心差异。

于 2011-02-13T23:08:35.140 回答
3

我会让我的服务器 fork() 并立即setuid(uid)放弃 root 权限。然后任何文件操作都将代表您已成为的用户。由于您是服务器的子节点,因此您仍将保留请求(我假设响应)将继续的 accept()ed 子套接字。这(显然)需要守护进程的 root 权限。

在这种情况下,在进程之间传递文件描述符似乎不必要地复杂,因为子进程已经有了“请求”描述符。

于 2011-01-31T06:50:32.577 回答
2

让一台服务器在 previlegued 服务器端口上运行,并为登录系统的用户生成子进程。子进程应该放弃特权并模拟登录的用户。现在子进程不能再造成伤害了。

于 2011-01-31T05:03:36.410 回答