关注了就能看到更多这么棒的文章哦~

Sticky groups in the shadows

By Jonathan Corbet
May 14, 2021
DeepL assisted translation

Linux 里经常需要用 group 权限来管理对某些资源的访问权。比如可以按 group 权限来配置对于共享目录、打印机等的访问,或是否能使用 sudo 等工具。不过,也可能需要根据 group 来拒绝对某一资源的访问,有一些管理员就使用了这个功能。但是,要想让 group 用来做这种反向的权限限制,必须要求用户不能随意脱离 group。有时候会出现一些方法能退出 group,于是在这些使用 group 来阻止访问资源的系统上就会出现漏洞。尽管过去进行了一些 fix,但事实证明,group 和 user 的 namespace 仍然存在至少一个潜在问题。Giuseppe Scrivano 提出了一个 patch set 试图通过创建 "shadow" group 来消解这个问题。

利用 group 成员身份,有两种方法可以阻止用户访问资源。其中一种只要简单地将文件的 group owner 设置为要拒绝的 group,然后将权限设置为不允许此 group 访问就好了。这个 group 的成员将无法访问该文件,即使全局权限(world permissions)允许。另一种方法是使用访问控制列表(access control lists,ACL)来明确地拒绝某些 group。同样,这些 group 中的任何进程都不允许访问。

我们可以复习一下相关知识,Linux 有两个不相关的 group 概念。如果没有什么其他限制的话,会将 "primary group" 或者说 "effective group ID" 用来设置新建出的文件属于哪个 group。这曾经是 Unix 系统中唯一一种与进程绑定的 group 概念,setgid() 就是用来设置这个 group 信息的。而 "supplementary group"组则是新增加出来的,它允许一个进程同时属于多个 group。supplementary group 是由 setgroups() 来设置的。设置拒绝访问名单的时候,通常都是(不一定全是)采用 supplementary group 来控制的。

不过,如果允许进程随意退出 group 的话,这种 "禁止访问的 group 列表" 方式的访问控制技术就是一个明显漏洞了。setgid()和 setgroups() 都是特权用户才能做的操作,所以正常情况下,普通进程不能修改 group 成员关系。至少,在 user namespace 出现之前是这样的。

早在 2014 年就发现了一个关于 user namespaces 和 groups 相关的问题。任何用户都可以创建一个 user namespace,并在该命名空间内以 root 身份运行。因此,在此命名空间外会被阻止的操作(例如 setgroups())在命名空间内执行时就不受限制了。因此,user namespace 很容易被用户利用来退出相关的 group 从而绕过限制。攻击者只需要要创建一个 namespace,然后调用 setgroups(),在 namespace 内退出该 group 即可。user namespace 最开始设计的时候确保了它内部的改动不会导致在 namespace 之外增加权限,但是,这种删除权限的做法不在保护范围之内。

当时所采用的解决方案是在每个进程的/proc 目录下添加一个控制文件(名为 setgroups)。在该文件中写入 "deny" 的话就可以确保包含了这个进程的 user namespace 内的任何进程都不能执行 setgroups(),连相关衍生出来的 user namespace 也受这个限制。而写入 allow 的话,就可以启用 setgroups()。这个操作必须在设置 namespace 的 group ID 映射关系之前就完成,否则写入 group ID 映射关系时就会启用 setgroups()。采用这个策略是为了便于验证。对于一个 namespace 来说,可以找到唯一一个位置来启用 setgroups()。如果 setgroups()被显式地禁用了,那么在 namespace 的生命周期内它都会一直保持这种状态,没有办法再次启用它。

这个 fix 解决了问题,但也付出了代价,毕竟 setgroups() 在很多情况下是有需要的,有一些合法的场景会需要调用这个 API。如果拒绝使用 setgroups() 的话,虽然可以防止进程在 namespace 中退出一些 group,但也导致了完全无法修改 supplementary group 了。

为了进行补救,Scrivano 提出的这组 patch set 为 setgroups 文件增加了另一个可选值,就是 "shadow"。写入 shadow 的话,跟写入 allowed 的效果相同,即 setgroups() 系统调用在 namespace 内是允许的,但还有一个区别。当 setgroups 文件被写入时,内核会立刻把这个进程的 supplementary group 信息复制下来,并将其与 namespace 存储在一起。每当 setgroups() 被调用时,这个 "shadow" group 的列表会被添加到 setgroups()的参数中的 group 列表之上,于是效果上就是只会增加所在的 group,而不能减少。

换句话说,"shadow" 模式将最开始设置的 supplementary group 信息固定下来。namespace 中具有相应权限的进程仍可以使用 setgroups() 来添加新的组,也可以删除那些不属于初始组的 group。但是在 namespace 被创建时设定的那些 supplementary group 将一直存在,即使进程可能看不到这个信息,因为 getgroups() 不会列出那些没有不在 setgroups() 参数中的组。

这个 patch 已经存在一段时间了。过去,大家看不到有什么好处,并且感觉还引入了一些复杂性。不过,最近开始有用户请求这一功能了,因此这项工作重新受到了关注。这次它是否能成功进入 mainline 还有待观察,但似乎总有一些用户希望既能使用这种 "negative group protection",同时又能在 user namespace 内改变 group 成员资格。