MinorFs

作者:Rob Meijer

MinorFs 是一组协同工作的用户空间文件系统,它们与 AppArmor 协同工作,提供一种灵活的自主访问控制形式,该控制在进程级别上运行。这种类型的进程级权限限制大致等同于面向对象编程中看到的权限限制,通过参数传递提供最小权限限制,而无需像 SELinux 等机制中看到的策略控制的管理开销。最小权限也称为最小特权或 POLA(最小权限原则)。

在 Linux 中,对文件系统数据的访问由两种不同的访问控制机制管理。首先是基本的、熟悉的 UNIX 自主访问控制系统。《美国国防部可信计算机系统评估标准》(又名“橙皮书”)将自主访问控制定义为“一种基于主体身份和/或他们所属的组来限制对对象的访问的方法。这些控制是自主的,因为具有特定访问权限的主体能够将该权限(可能是间接地)传递给任何其他主体(除非受到强制访问控制的约束)”。

Linux 还通过 Linux 安全模块 (LSM) 接口提供访问控制。LSM 为额外的访问控制机制(例如强制访问控制)提供挂钩,同时保持基本的 UNIX 自主访问控制机制不变。“橙皮书”将强制访问控制定义为“一种基于对象中包含的信息的敏感性(由标签表示)以及主体访问此类敏感性信息的正式授权(即,许可)来限制对对象的访问的方法”。

这两个构造以限制性方式组合,这意味着如果其中任何一个拒绝访问,则访问将被拒绝。LSM 接口的著名用户是 Security-Enhanced Linux (SELinux)(在 Debian 和 Red Hat 中使用)和 AppArmor(在 SUSE 和 Ubuntu 中使用)。

尽管 UNIX 文件系统访问的自主访问控制在几十年中一直保持在相同的(简单用户级别)粒度,但强制访问控制已变得更加细粒度(进程级别)。然而,这种粒度带来了相对较大的管理成本。例如,SELinux 在许多管理员中以维护配置文件的大量开销而闻名。

面向对象提供模型

在设计和编写面向对象 (OO) 程序时,避免全局变量、使用数据隐藏、在对象之间传递引用以及使用已建立的设计模式(如代理和工厂)是我们习惯和舒适的概念,我们大多数人都开始欣赏这些技术提供的许多优势。然而,当使用这些概念时,我们许多人未能意识到的是,我们所做的一部分可以被认为是访问控制。

如果我们从访问控制的角度来看待 OO 范例,很容易看出 OO 程序使用的模型既是自主的适用于最高粒度。因此,您可以说 OO 程序在内部使用极其细粒度的自主访问控制形式。但是,我们必须注意,这种访问控制形式实际上比面向对象编程的整个概念还要古老。OO 程序员隐式使用的访问控制机制实际上在很大程度上等同于所谓基于能力系统中使用访问控制机制。能力(通常称为密钥)是一种不可伪造的授权令牌,可以在程序之间传递。在基于能力的系统中,拥有能力使您有权在与能力相关的权利指定的边界内使用引用的对象。使用能力,无需检查其他访问控制机制(例如,ACL);能力本身包含所有必要的信息。

那么,为什么不在稍微粗糙的粒度级别上对进程访问文件和目录使用相同形式的自主访问控制呢?MinorFs 的目标正是做到这一点,并在 AppArmor 的大力帮助下实现。

首先,让我们看看 OO 设计和编程中使用的类、对象和成员数据,与程序、进程和文件系统数据相比如何。有明显的迹象表明,我们可能正在处理不同粒度级别上的同一组抽象。

您可以像看待类一样看待程序。进程是程序(磁盘映像)的实例,就像对象是类的实例一样。大多数对象都有状态,就像大多数进程都有状态一样。您可以说,在对象级别粒度和进程级别粒度上都存在相同的抽象。

接下来,我们需要将持久的磁盘目录结构映射到我们刚刚用于建模程序和进程的相同 OO 模型。要实现这一点,需要克服几个障碍。首先,是进程持久性,也就是说进程是“非”持久性的,那么它们如何适应模型呢?

其次,是按引用传递。如果一个对象想要与它认识的另一个对象共享其部分私有状态,则该对象可以传递其内部状态的一部分的副本或引用。然而,进程在很大程度上仅限于传递副本,而不是引用。

进程持久性

程序是持久的;目录和文件是持久的,但进程不是。这种不匹配使得无法将任何持久的磁盘数据存储添加到由进程 ID 标识的进程中,因为当进程结束时,进程 ID 将不再有效。允许进程级别的 OO 类抽象用于持久磁盘存储的基本解决方案是将进程定义为所谓的伪持久进程的化身。因此,现在,程序仍然等同于类;伪持久进程是对象的持久等价物,而磁盘上的持久目录和文件等同于成员数据字段。使用伪持久进程这个新概念,使我们能够将 AppArmor 的磁盘数据访问控制功能提升到超出强制访问控制可能达到的粒度级别——即伪持久进程的粒度,但我们没有集中式或人工管理的负担,也没有强制访问控制所体现的管理开销。

按引用传递

OO 语言中的对象可以按引用传递,但 Linux 上的大多数 IPC 不允许进程之间按引用传递。早期 UNIX 工程师做出的一个有见地的例外是创建了通过 UNIX 套接字传递文件句柄的能力。您可以说像这样使用的文件句柄完全是按引用传递。在能力系统中,这样的引用称为受保护能力或对象能力。

目前,目录文件句柄不能用作受保护能力。为了克服这个问题,能力系统历史中有一个非常有用的概念。这个概念是使用稀疏密钥字符串作为引用的表示。也就是说,我们创建一个相对较长的稀疏密钥字符串,既指定资源又授权访问该资源。此字符串称为稀疏能力或非受保护能力。这种类型的能力在某种程度上不如 UNIX 文件句柄示例中的受保护类型。当与 AppArmor 的保护相结合时,它仍然具有许多属性,使其使用方式大致等同于面向对象语言中引用的使用方式。

AppArmor

AppArmor 是 SUSE 和 Ubuntu Linux 中使用的纯粹许可式强制访问控制系统。MinorFs 使用 AppArmor 作为其基础,并以这种方式扩展 AppArmor,使其可以以自主甚至基于能力的方式使用。尽管 MinorFs 可以与 AppArmor 分开使用,但其可用性相对有限。MinorFs 在没有 AppArmor 的情况下可用性有限的主要原因是,默认情况下,进程可以通过 /proc/$PID 目录访问其他进程的数据(如环境变量或命令行参数),这(根据 MinorFs 的理念)应被视为进程私有的。

这意味着在没有 AppArmor 的情况下,进程在某些情况下将能够通过 proc 文件系统窃取彼此的能力。虽然 AppArmor 修复了默认 proc 文件系统访问权限带来的漏洞,但 MinorFs 扩展了 AppArmor。MinorFs 提供的访问控制机制使用动态最小权限方法扩展了 AppArmor 提供的静态最小特权方法。也就是说,它增加了委托分解和/或衰减权限的能力。

委托

SELinux 和 AppArmor 之间最重要的区别之一是 SELinux 是基于标签的,而 AppArmor 是基于路径的。基于路径的安全性存在两个被广泛讨论的问题:一个是临时文件(可以通过使用 MinorViewFs 临时条款来解决),第二个是硬链接。感知到的硬链接问题是,有权访问文件的实体可以创建硬链接,这将委托对该文件的访问。委托有许多合法的用途,因此,基于能力的安全性的倡导者建议始终允许委托。为了有效地使用委托,仅委托最小权限。在这种情况下,最小权限意味着始终委托最小且尽可能衰减的子图,该子图仍然可以完成工作。

AppArmor 帮助我们强制执行的基于能力的安全性的主要属性是,进程不应访问等同于全局变量的内容。如果我们从进程级别的粒度来看待 UNIX 系统中的 temp 和 home 目录,那么它们在许多方面都可以被视为全局变量。

AppArmor 配置文件的工作方式是,它定义了特定应用程序可用的权限列表。为了方便起见,AppArmor 还提供了使用单个 include 指令包含权限集的功能。

在设计将使用 MinorFs 的系统时,您应始终首先设计您的权限分离设置。不要让您的应用程序变成一个庞然大物。

使用 AppArmor 和 MinorFs,您可以根据 OO 或能力范例构建权限分离的应用程序,但即使是较小的步骤也可能非常有用。在安装时,MinorFs 创建一个指向 /bin/bash 的硬链接,名为 /bin/minorbash,它具有以下 AppArmor 配置文件

#include <tunables/global>

/bin/minorbash {
   #include <abstractions/base>
   #include <abstractions/bash>
   #include <minorfs/systemreadonly>
   #include <minorfs/full>
}

此配置文件基本上为名为 minorbash 的 bash 版本及其启动的所有程序提供了大量的只读权限,但没有写入权限。这意味着,您只需从使用 minorbash 而不是 bash 的 shell 脚本启动程序,即可使用降低的访问权限运行程序。

MinorFs

现在,对于 MinorFs 本身。MinorFs 目前由两个用户空间文件系统组成。这些文件系统是使用 FUSE Perl 模块实现的相对简单的 Perl 脚本。每个文件系统都有自己独特的任务。FUSE(用户空间文件系统)是一个内核模块,允许非特权用户创建自己的文件系统。

MinorCapFs

MinorCapFs 是 MinorFs 的核心。不久前,Linux 目录和文件访问 API 扩展了一组新的调用——openat()、mkdirat() 等——它们采用了一个额外的第一个参数,即文件描述符,该描述符指定应从何处解析相对路径(这些调用将在未来版本的 POSIX 中标准化)。鉴于 Linux 中的文件句柄可以在进程之间通信并用作能力,因此查看新的目录句柄调用并创建或扩展 LSM 模块,以便可以将目录句柄作为目录能力传递,这似乎是一个好主意。主要目标是将目录句柄用作目录的能力,该能力不会泄露有关父目录的任何信息。

在与 AppArmor 人员讨论我的想法后,得出的结论是我应该尝试在用户空间中尽可能多地完成工作,所以我开始设计 MinorCapFs。MinorCapFs 的目标是允许子图的(未衰减)分解、委托和组合。MinorFs 为每个目录树子图定义一个稀疏能力。

为了让您或您的程序分解目录图,每个文件和目录都给出了一个名为 cap 的扩展属性。此扩展属性保存完整的 MinorCapFs 路径,其中包含目录子图的稀疏能力。使用您可用的任何形式的进程间通信,此路径可以与任何进程甚至同一系统上的其他用户共享。接收用户或进程可以在另一个目录子图中创建一个符号链接——例如,为了使委托永久化。

图 1 显示了如何使用 attr 命令来获取 cap 属性,以及如何将此属性用作目录或文件的短而强的路径或稀疏能力。通常,您不应为此使用命令行,而应从您的程序代码中执行相同的操作。getxattr 函数可用于执行与上述示例中 attr 命令相同的操作。

MinorFs

图 1. MinorCapFs 扩展属性

组合几乎与分解一样重要。扩展属性用于分解的用法可能很奇怪和新颖,但组合使用了一种我们可能都更熟悉的构造,即使用符号链接的构造。除了分解之外,MinorCapFs 还提供了以与我们习惯的文件系统相同的方式创建符号链接的功能。因此,MinorCapFs 结合了两个基本功能,用于执行目录树图的简单未衰减分解,以及用于从子图执行目录图的组合。

您可以说 MinorCapFs 提供了最简单的裸级别形式的未衰减的基于能力的访问控制。但是,谁持有顶级能力?以及如何将子图委托给各个进程?这就是第二个文件系统发挥作用的地方。

MinorViewFs

由于 MinorCapFs 提供了树图分解和组合构造,因此必须将稀疏能力传递给进程,以便任何进程都能够使用 MinorCapFs。

为了了解我们如何需要解决这个问题,让我们退后一步,看看我们试图利用的并行性。我们试图将进程变成一种更粗粒度的对象形式,就像任何 OO 语言中的对象一样,它们都有私有数据成员。有两种方法可以看待进程本身。首先,是传统的非持久进程视图,其中进程持有的所有状态在系统重新启动或因任何其他原因结束时都会消失。您可以将这种形式的委托视为临时目录的麻烦用法的更好替代方案。默认情况下,临时文件将成为进程私有的,直到进程将它们显式委托给其他进程。

重要的是要注意,MinorViewFs 的临时条款不是引用计数垃圾回收系统。在拥有它的非持久进程死亡时,委托的子图将立即失效。

MinorViewFs 通过 /mnt/minorfs/priv 下的两个符号链接将子图委托给各个进程(图 2)。每个读取这些符号链接的进程都将拥有一组完全不同的委托给它的子图稀疏能力。第二个符号链接 /mnt/minorfs/priv/tmp 指向上述临时子图。

MinorFs

图 2. MinorViewFs 链接

根据 MinorViewFs 的伪持久进程

尽管将临时子图委托给进程相对简单,但同一个进程作为某个伪持久进程的化身的概念需要更多思考。

MinorViewFs 基于所谓的第 n 次声明基础查看伪持久进程。它基本上归结为,如果在程序已经运行的两个较早实例化的版本正在运行时实例化程序,则新进程将声明第三个槽。如果系统重新启动,您还需要重新启动程序的第一个和第二个实例。

尽管适用于类守护进程程序,但这对于编辑器和其他用户驱动的程序来说确实可能不方便。为了解决这些问题,并解决脚本和 Java 程序都是同一程序的实例所带来的问题,MinorViewFs 使用一些简单的技巧来确定程序,更具体地说,是基于程序调用的身份。

那么 MinorViewFs 如何确定程序调用身份呢?首先,有进程父链。进程父链,包括程序和这些程序加载的库,有助于调用的唯一身份。如果父链不足以作为调用身份,则系统管理员可以在 /etc/minorfs/ 下添加配置文件。

这是一个 E 语言解释器的配置文件示例

<codefile path="/usr/local/e/e.jar" cmdline="true" slots="256">
    <env>DISPLAY</env>
</codefile>

示例配置将命令行添加到程序调用的识别属性中。因此,通过使用可选的配置文件,MinorViewFs 能够创建和重新创建唯一标识的数据集,使其能够将子图重新委托给同一程序的新化身。

上面命名的 E 语言更进一步;它允许将 E 程序中的大型子系统放在一起,并自动序列化和同步到磁盘存储。更重要的是,E 语言是一种对象能力语言;因此,将 AppArmor 和 MinorFs 与 E 语言相结合,使您可以将最小权限和私有存储一直组合到对象级别的粒度。尽管 E 语言有点深奥,但它是一种成熟而完整的语言,在进行高完整性项目时值得考虑。

当进程启动并访问 /mnt/minorfs/priv/home 符号链接时,此符号链接将指向与上次将程序调用到同一槽时相同的 MinorCapFs 子图。

除了对以权限分离和最小权限为中心设计的新程序有用之外,MinorViewFs 还可以与 SSH 客户端等旧程序一起使用。但是,这确实涉及使用 admin 工具 2rulethemall,该工具可帮助用户使用每个用户的密码绕过基本的基于进程的访问控制机制。您可以将未受保护的 SSH 私钥放在 SSH 客户端的私有持久存储空间中。同样,除了 MinorViewFs、SSH 或 2rulethemall 之外,任何非 root 用户运行的程序都无法访问私钥。

结论

MinorFs 为您的 AppArmor 化 Linux 系统带来了极端(基于能力)形式的自主访问控制。它使用一种访问控制形式,将委托视为对安全性有益的事情。尽管 MinorFs 仍在开发中,并且不完整,但它应该已经提供了一种有用且直观的方式来创建使用文件系统访问的权限分离程序。它提供了一种保护存储在磁盘上以供持久进程使用的序列化数据的方法,以及一种保护进程私有数据的方法。而且,它是临时目录的麻烦用法的替代方案。

即将推出的 MinorFs 版本将包括第三个文件系统 MinorCtkrFs,它将基于所谓的 Caretaker 模式以通用方式实现衰减。此 MinorCtkrFs 应为文件和目录添加不同类型的只读能力,以及可撤销的读/写和只读能力。

Rob Meijer 是来自荷兰的计算机取证和安全软件开发专业人士。他的职业生涯始于 UNIX 系统管理员,十年前转行从事软件开发。在他的业余时间,他正在从事多个与最小权限相关的开源项目,包括 MinorFs。

加载 Disqus 评论