SafeSetID¶
SafeSetID 是一个 LSM 模块,它控制 setid 系列系统调用,以限制从给定 UID/GID 到仅由系统范围允许列表批准的 UID/GID 的转换。这些限制还禁止给定的 UID/GID 获取与 CAP_SET{U/G}ID 相关的辅助权限,例如允许用户设置用户命名空间 UID/GID 映射。
背景¶
在缺少文件能力的情况下,需要在 Linux 系统上切换到其他用户的进程必须使用 CAP_SETUID 权限生成。CAP_SETUID 授予以 root 身份运行的程序或以非 root 用户身份运行的程序,这些程序已被明确赋予 CAP_SETUID 运行时能力。通常最好使用 Linux 运行时能力而不是文件能力,因为使用文件能力以提升的权限运行程序会打开可能的安全漏洞,因为任何有权访问该文件的用户都可以 exec() 该程序以获得提升的权限。
虽然可以通过赋予完整的 CAP_SET{U/G}ID 能力来实现进程树,但这通常与首先在非 root 用户下运行进程树的目标相悖。具体来说,由于 CAP_SETUID 允许更改为系统上的任何用户,包括 root 用户,因此对于这种情况来说,它是一种过度的能力,特别是考虑到程序通常只调用 setuid() 来将权限降级为较低权限的用户——而不是提升权限。不幸的是,在 Linux 中,除了允许切换到系统上的任何用户之外,没有一般可行的方法来限制用户可以通过 setuid() 切换到的潜在 UID。此 SafeSetID LSM 旨在为此类限制 setid 能力提供解决方案。
此 LSM 的主要用例是允许非 root 程序在没有完全 CAP_SETUID 能力的情况下转换为其他不受信任的 uid。非 root 程序仍然需要 CAP_SETUID 来进行任何类型的转换,但是此 LSM 施加的额外限制意味着它是 CAP_SETUID 的“更安全”版本,因为非 root 程序不能利用 CAP_SETUID 来执行任何未经批准的操作(例如,设置 uid 为 0 或创建/进入新的用户命名空间)。更高层次的目标是允许对系统服务进行基于 uid 的沙箱化,而不必仅仅为了让非 root 程序可以降级到权限更低的用户而到处分发 CAP_SETUID。当系统上的一个非 root 守护程序应该被允许以不同的 uid 生成其他进程时,这一点尤其重要,但是不希望给予守护程序一个基本上等同于 root 的 CAP_SETUID。
考虑的其他方法¶
在用户空间中解决此问题¶
对于希望拥有此 LSM 中实现的受限 setid 功能的候选应用程序,另一种选择是从应用程序中完全取消 setid 功能,并重构应用程序中的进程生成语义(例如,通过使用特权辅助程序来执行进程生成和 UID/GID 转换)。不幸的是,围绕进程生成存在许多语义会受到影响,例如 fork() 调用,其中程序在 fork() 后不立即调用 exec(),父进程为生成的子进程指定自定义环境变量或命令行参数,或者跨 fork()/exec() 继承文件句柄。因此,使用用户空间中特权辅助程序的解决方案可能不太吸引人,无法合并到依赖 Linux 中某些进程生成语义的现有项目中。
使用用户命名空间¶
另一种可能的方法是在其自己的用户命名空间中运行给定的进程树,并为树中的程序提供 setid 功能。通过这种方式,树中的程序可以在其自身的用户命名空间中更改为任何所需的 UID/GID,并且只有经过批准的 UID/GID 可以映射回初始系统用户命名空间,从而有效地防止权限提升。不幸的是,通常无法单独使用用户命名空间,而不会将其与其他命名空间类型配对,这并非始终是一种选择。Linux 根据“拥有”某些实体的用户命名空间检查能力。例如,Linux 有一个概念,即网络命名空间由创建它们的用户命名空间拥有。这样做的结果是,对访问给定网络命名空间的能力检查是通过检查任务在拥有网络命名空间的用户命名空间上下文中是否具有给定的能力来完成的,而不一定是在给定任务运行的用户命名空间下完成的。因此,在新用户命名空间中生成进程实际上会阻止它访问由初始命名空间拥有的网络命名空间。这对于任何希望保留 CAP_NET_ADMIN 功能以调整网络配置的应用程序来说都是一个致命的缺陷。单独使用用户命名空间会导致其他系统交互方面的问题,包括使用 pid 命名空间和设备创建。
使用现有的 LSM¶
其他任何树内 LSM 都不具备控制 setid 转换的能力,甚至根本不使用 security_task_fix_setuid 钩子。SELinux 对该钩子说:“由于 setuid 只影响当前进程,并且由于 SELinux 控制不基于 Linux 身份属性,因此 SELinux 不需要控制此操作。”
使用说明¶
此 LSM 挂钩 setid 系统调用,以确保在存在适用的限制策略的情况下允许转换。通过写入安全文件系统中安全文件系统挂载位置的 safesetid/uid_allowlist_policy 和 safesetid/gid_allowlist_policy 文件来配置策略。添加策略的格式为 ‘<UID>:<UID>’ 或 ‘<GID>:<GID>’,使用文字数字,并以换行符结尾,例如 ‘123:456n’。写入空字符串 “” 将刷新策略。再次强调,为 UID/GID 配置策略将阻止该 UID/GID 获取辅助 setid 权限,例如允许用户设置用户命名空间 UID/GID 映射。
关于 GID 策略和 setgroups() 的说明¶
在 v5.9 中,我们添加了对限制 CAP_SETGID 权限的支持,就像之前对 CAP_SETUID 所做的那样。但是,为了与用户空间中常见的沙箱相关代码约定兼容,我们目前允许对具有 CAP_SETGID 限制的进程进行任意 setgroups() 调用。在我们未来的版本中添加对限制 setgroups() 调用的支持之前,这些 GID 策略不会增加任何有意义的安全性。一旦我们有了策略检查代码,将强制执行 setgroups() 限制,这将依赖于 v5.9 中添加的 GID 策略配置代码。