Skip to content

Files

Latest commit

481eb0e · Dec 26, 2021

History

History
253 lines (150 loc) · 19.8 KB

File metadata and controls

253 lines (150 loc) · 19.8 KB

二、强制访问控制和 SELinux

第 1 章Linux 访问控制中,我们介绍了自主访问控制系统的一些缺点。在这些系统中,对象的所有者完全控制其权限标志,并且当作为root执行或具有某些能力时,可以展示更大的能力(例如,chown的能力)。在本章中,我们将:

  • 检查 MAC 的基础知识
  • 介绍一些 SELinux 的行业驱动程序
  • 讨论标签、用户、角色和类型
  • 探索允许和约束对象交互的有形策略的实现

理想的 MAC 系统保持了对内核资源(如文件)提供明确访问控制的特性,而不管对象的所有者是谁。例如,在 MAC 系统中,对象的所有者可能无法完全控制其权限。在 Linux 中,MAC 框架与当前的 DAC 控件正交工作。这意味着媒体访问控制控制不干扰数模转换器控制。换句话说,为了避免媒体访问控制和数模转换器系统之间的潜在冲突,内核在检查媒体访问控制权限之前,使用数模转换器权限验证访问。如果数模转换器权限导致权限冲突,则永远不会检查媒体访问控制权限。只有当数模转换器权限通过时,内核才会根据媒体访问控制权限提供程序验证访问。任何一级的失败都将导致EACCESS的返回。如果数模转换器和媒体访问控制权限通过,那么内核资源(例如,文件描述符)将被发送回用户空间。

在 Linux 中,一个名为 Linux 安全模块 ( LSM )的框架在 Linux 2.6.x 系列内核期间被合并。该框架允许您通过将 LSM 挂钩与安全提供者绑定,在构建时选择中启用强制访问控制系统。安全增强 Linux ( SELinux )是内核内这个 MAC 安全框架的第一个消费者,是一个强制访问控制系统的实现。SELinux 附带了各种各样的 Linux 系统,例如红帽企业版 Linux ( RHEL )以及 Fedora。最近,它已经开始搭载安卓系统。SELinux 的源代码可以在kernel/security/selinux下的 Linux 源代码树中找到,希望查看的人可以查看。

回到基础

SELinux 是由美国政府和犹他大学设计的设计的重新实现,被称为 FLUX 高级安全内核 ( FLASK )。SELinux 和 FLASK 架构提供了一个中央策略文件,用于确定访问控制决策的结果。这个中心策略是白名单形式的。这意味着所有访问控制规则都必须由策略文件明确定义。这个策略文件是由一个称为安全服务器的软件组件抽象和提供的。当 Linux 内核需要做出访问控制决定并且 SELinux 被启用时,内核通过 LSM 钩子与安全服务器交互。

在运行的系统中,进程是在中央处理器上获得时间来执行任务的活动实体。用户只是调用这些过程来代表他们完成工作。这是一个重要的概念。当我们键入这本书时,我们相信运行在我们机器上的文字处理器不会打开我们的 SSH 密钥并将其嵌入到文档元数据中。现在,这个过程控制的是计算资源,而不是用户。进程是运行的实体,它是为资源向内核进行系统调用的进程,而不是物理的人。考虑到这一点,SELinux 系统中的第一个参与者是过程,通常被称为主体。它是访问文件的主体。这是安全服务器用来做出访问决策的主题。

因此,主题利用核心资源。这种内核资源是目标的一个例子。主体对目标执行动作。自然,人们应该问,“一个主体做什么动作?”这些称为访问向量,通常与执行的系统调用的名称相关。例如,受试者可以对目标执行open操作。需要注意的是,目标也可以是过程。例如,如果系统调用是ptrace,主题可以是类似于调试器的东西,目标可以是您希望调试的进程。主题通常是一个进程,但目标可能是一个进程、套接字、文件或其他东西。

标签

SELinux 提供了使用标签描述与目标和主题相关的策略的语义。标签是与对象相关联的元数据,用于维护主题和目标的访问信息。与此对象关联的数据是一个字符串。回到调试器的例子中,gdb进程可能有一个主题标签串debugger,目标可能有一个标签debugee。然后在安全策略中,可以使用一些语义来表示允许带有主题标签debugger的进程调试带有目标标签debugee的应用。

幸运的是,也许不幸的是,SELinux 没有使用这样简单的标签。事实上,标签由四个冒号分隔的字段组成:用户、角色、类型和级别。这种额外的复杂性提供了非常灵活的控制选项。

用户

标签中的第一个字段标识用户。用户字段用作基于用户的访问控制 ( UBAC )设计的一部分。然而,这个并不像数模转换器中的用户概念那样与人类用户相关联。SELinux 用户通常定义一组传统用户。一个常见的例子是将所有正常用户识别为 SELinux 用户,user_u。也许是系统流程的独立用户,如system_u。按照桌面 SELinux 社区的惯例,字符串的用户部分以_u为后缀。

角色

标签中的第二个字段是角色。该角色被用作基于角色的访问控制 ( RBAC )的设计。角色用于向用户提供额外的粒度。例如,假设我们为管理员保留了用户字段sysadm_u。管理员可能在不同的任务中,根据不同的任务,用户在sysadm_u中的角色(以及权限)可能会发生变化。例如,当管理员需要装载和卸载文件系统时,角色字段可能会更改为mount_admin_r。当管理员设置iptables规则时,角色可能会更改为net_admin_r。角色允许在正在执行的任务范围内隔离权限。

类型

类型是冒号分隔标签的第三个字段。在 SELinux 访问控制模型的类型实施 ( TE )部分,类型字段被评估。TE 是驱动 SELinux 安全功能的主要组件,也是策略开始生效的地方。

SELinux 基于白名单系统,默认情况下一切都被拒绝,并且需要策略的明确批准才能进行交互。该批准最初是通过引用主体和目标类型的允许规则从策略中确定的。SELinux 类型也可以被分配属性。属性允许您给许多类型一组通用的规则。属性可以帮助最小化类型的数量,并且可以以类似于继承模型的方式使用。

访问向量

数据是由进程通过系统调用和可能的用户定义访问方法访问的。用户定义的访问方法通常通过用户空间对象管理器来控制。这些访问路径也称为向量,组成了一组可应用于对象的动作。例如,如果一个进程打开一个文件,将一些数据写入该文件,然后读回该文件,则执行的访问向量将是openreadwrite。如果一个进程调试另一个进程,访问向量将是ptrace

多级安全

SELinux 还支持多级安全 ( MLS )模式,向贝尔-拉普杜拉 ( BLP )模式致敬,但也可以使用替代模式。BLP 模式的建立是为了正式确定国防部的安全政策。例如,一个有秘密权限的人不应该能够阅读绝密材质。然而,让我们假设这个人有一个辉煌的想法,最终需要在绝密级别得到保护;这些数据可以被“高度机密”为绝密。这被称为“不读不写”。

该字段的 SELinux 实现有子字段。第一个领域是敏感性,并将永远存在。在前面的例子中,相关的敏感性包括秘密和绝密。第二个子字段是类别,可能不存在。这些领域在政府分类的背景下也有意义。数据本身可能是被划分的,所以虽然敏感性是相同的,例如绝密,但数据应该只传播给同一部门或类别的人。敏感性是通过优势关键字以分级方式定义的。在典型的政策中,s0是最低的敏感度,sN是最高的n > 0。因此,s1s0更敏感。类别是集合。与水平相关的控制,包括敏感性和潜在的类别,遵循集合论的概念,如优势和平等。在 MLS 安全中,默认情况下允许所有交互,这与类型强制不同。敏感度和类别都可以排列,类别可以枚举。因此,一个标签可能有一些敏感性和不同的类别数。

放在一起

SELinux 标签相当灵活,有时也很复杂。从一个人为的例子开始通常是有益的,这个例子关注类型强制。稍后,随着对更精细粒度的需求变得更加明显,我们可以添加额外的字段。方便的是,你可以将这个模型投射到日常生活的场景中,为材质提供一些可触知的感觉。著名的 SELinux 人物丹·沃尔什(Dan Walsh)发布了一篇博文,用宠物作为类比。让我们继续这个前提,但是我们将做一些修改,定义我们自己的例子。最好从简单的类型强制开始,因为它最容易理解。

你可以在http://opensource.com/business/13/11/selinux-policy-guide阅读丹·沃什介绍宠物类比的原创博文。

假设我们有一只猫和一只狗。我们不想让猫吃狗粮。我们不想让狗吃猫粮。此时,我们已经确定了两个对象,一只猫和一只狗,以及两个目标,猫粮和狗粮。我们还发现了一个接触媒介,吃。我们可以使用允许规则来实施我们的策略。可能的规则如下:

allow cat cat_chow:food eat;
allow dog dog_chow:food eat;

让我们使用这个例子来开始并定义一个基本的语法来表达我们想要实施的访问控制。第一个标记是allow,表示我们希望允许主体和目标之间的交互。狗被指定为类型,猫被指定为 T2。猫粮分cat_chow类,狗粮分dog_chow类。这种情况下的访问向量是eat。使用这个基本语法,也是有效的 SELinux 语法,我们将动物限制在它们应该吃的食物上。请注意类型后的:food注释。这是目标对象的类字段。例如也可能有dog_chow treatcat_chow类,它们可能表明我们希望以一种可能不同于我们允许获取非零食的方式来获取零食。

假设我们多了两条狗,我们的场景有三条狗。这些狗有不同的大小:小的、中的和大的。我们想确保这些新来的狗都不吃别人的食物。我们可以做一些事情,比如为每只狗创造一种新的类型,防止狗吃其他狗的食物。它看起来像这样:

allow cat cat_chow:food eat;
allow dog_small dog_small_chow:food eat;
allow dog_medium dog_medium_chow:food eat;
allow dog_large dog_large chow:food eat;

这会起作用;然而,种类的总数将很难管理,如果我们允许大狗吃小品种的食物,这种情况将继续增长。我们可以做的是使用 MLS 支持为每个目标或狗粮碗指定一个敏感度。让我们假设以下情况:

  • 猫粮碗有灵敏度,tiny
  • 小狗的食碗有灵敏度,small
  • 中型狗粮碗有灵敏度,medium
  • 大狗的食碗有灵敏度,large

我们还需要确保受试者也贴上了适当敏感度的标签:

  • 猫要有敏感度,tiny
  • 小狗要有敏感度,small
  • 中型犬要有灵敏度,medium
  • 大狗要有灵敏度,large

此时,我们需要引入额外的语法来允许交互,因为默认情况下,MLS 允许一切,而 TE 拒绝一切。我们将使用mlsconstrain,来限制系统内的交互。规则可能是这样的:

mlsconstrain food eat (l1 eq l2);

这种限制只允许受试者以相同的敏感度吃食物。SELinux 定义了关键字l1l2l1关键字是目标的级别,l2是源的级别。因为这些规则是白名单的一部分,这也阻止了受试者吃没有同等敏感度的食物。

现在,假设我们又得到了一只大狗。现在我们有两只大型犬。然而,他们有不同的饮食,需要获得不同的食物。我们可以添加一个新类型或修改一个现有类型,但这将有同样的限制,导致我们使用敏感性来阻止访问。我们可以增加另一个灵敏度,但是有large1large2的灵敏度可能会令人困惑。在这一点上,类别将允许我们在我们的控制中变得更加精细。假设我们添加了一个表示品种的类别。我们标签中的 MLS 部分看起来像这样:

large:golden_retriever
large:black_lab

这些可以用来防止黑实验室吃金毛猎犬的食物。现在假设你对另一只狗圣·伯纳德感到惊讶。假设这个新伯纳德可以吃任何大狗的食物,但是其他大狗不能吃他的食物。我们可以给食物碗和狗贴上标签。

|

狗的品种

|

主题标签

|

目标标签

| | --- | --- | --- | | 金毛猎犬 | Dog:large:golden_retriver | dog_chow:large:golden_retriver | | 黑色实验室乐队 | Dog:large:black_lab | dog_chow:large:black_lab | | 圣伯纳德 | Dog:large:saint_bernard, black_lab, golden_retriever | dog_chow:large:saint_bernard | | 猫 | Cat:tiny | cat_chow:tiny |

现有mlsconstraint需要修改。如果圣·伯纳德的食物用完了,去吃黑色实验室乐队的菜,圣·伯纳德就不能吃了,因为水平不相等(Dog:large:saint_bernard, black_lab, golden_retrieverdog_chow:large:black_lab不一样)。记住,层次是集合,所以我们需要引入一些概念,如果主体集合支配目标集合,那么应该允许这种交互。

这可以通过dom关键字来实现:

mlsconstrain food eat (l1 dom l2);

支配关键字dom不同于相等,表示l1l2的超集。换句话说,与目标l2相关联的级别属于与主体l1相关联的潜在更大的级别集。在这一点上,我们能够保持所有的食物分开,并以我们认为合适的方式使用。

得到所有这些狗后,你意识到是时候喂它们了,所以你得到一袋狗粮,并在每个碗里放一些。然而,在你可以添加狗粮到碗中之前,我们需要一些允许规则和标签,让你。记住,SELinux 是一个基于白名单的系统,所有的东西都必须被明确允许。

我们会给人类贴上human标签,定义一些规则。对...别忘了喂猫,还有:

allow human dog_chow:food put;
allow human cat_chow:food put;

我们还需要用所有的敏感性和类别来标记human,但是当我们需要在系统中添加额外的狗、品种和品种尺寸时,这将变得很麻烦。如果类型是human,我们可以绕过约束。用这种方法,我们总是相信human会把正确的食物放在合适的碗里:

mlsconstrain food eat (l1 dom l2);
mlsconstrain food put (t1 == human);

注意在 MLS 约束的访问向量中增加了put。维奥拉。人类现在可以喂养它不断成长的一群动物了。

所以你的生日到了,你会收到一个自动喂狗器作为礼物。您可以标记食品分配器dispenser并修改 MLS 约束:

mlsconstrain food eat (l1 dom l2);
mlsconstrain food put (t1 == human or t1 == dispenser);

同样,我们看到需要压缩类型的数量并组织起来,以避免重复行。这是属性非常方便的地方。我们可以通过首先定义属性来为我们的humandispenser类型分配属性:

attribute feeder;

然后我们可以将其添加到类型中:

typeattribute human, feeder;
typeattribute dispenser, feeder;

这也可以在类型声明中完成:

type human, feeder;
type dispenser, feeder;

此时,我们可以修改 MLS 语句,如下所示:

mlsconstrain food eat (l1 dom l2);
mlsconstrain food put (t1 == feeder);

现在假设你雇了一个女佣。你想确保女仆服务派出的任何人都能喂你的宠物。就此而言,让我们也让你的家人来喂养他们。这将是用户能力的一个很好的用例。我们将定义以下用户:adults_ukids_umaid_u。然后,我们需要添加一个约束语句来允许这些用户进行交互:

mlsconstrain food put (u1 == adults_u or u1 == maid_u);

这将阻止孩子们喂狗,而是让女仆和大人喂狗。现在假设你雇了一个园丁。您可以创建另一个用户gardener_u,或者您可以将用户折叠成几个类并使用角色。假设我们将gardener_umaid_u折叠成staff_u。园丁没有理由喂狗,所以我们可以使用基于角色的过渡来让员工在他们的职责之间移动。例如,假设员工可以执行一项以上的服务,也就是说,同一个人可以园艺和打扫。在这种情况下,他们可能会扮演gardener_rmaid_r的角色。我们可以使用 SELinux 的角色功能来满足这一需求:

mlsconstrain food put (u1 == adults_u or (u1 == staff_u and r1 == animal_care_r);

工作人员只能在狗处于animal_care_r角色时喂它们。如何进入和退出这个角色真的是唯一缺少的部分。你需要有一个定义明确的系统来指导员工如何进入动物护理角色,以及如何转换回来。SELinux 中的这些转换要么通过动态角色转换自动发生,要么通过源代码修改自动发生。我们假设任何人类实体(园丁、成年人、孩子)都是从human_r角色开始的。

动态角色转换使用由两部分组成的规则,第一部分允许通过允许规则进行转换:

allow human_r animal_care_r;

角色转换语句如下:

role_transition human_r dog_chow animal_care_r;
role_transition human_r cat_chow animal_care_r;

这是一个很好的例子,将dog_chowcat_chow类型归属于一个新的属性,animal_chow,并将前面的角色转换重写为:

typeattribute dog_chow, animal_chow;
typeattribute cat_chow, animal_chow;
role_transition human_r animal_chow animal_care_r;

有了这些角色转换,你只能从human_r角色转到animal_care_r。您还需要定义转换来返回。还需要注意的是,您可能会定义其他角色。假设你定义了gardener_r这个角色,当有人处于这个角色时,他们不能过渡到animal_care_r。假设你对这项政策的理由是园丁可能会使用对宠物不安全的化学物质,所以他们在喂养宠物之前需要洗手。在这样的情况下,他们应该只能从hand_wash_r的角色过渡到animal_care_r了。

复杂性和最佳实践

正如您现在所理解的,SELinux 是复杂的,可以被认为是一种通用的“元编程策略语言”。你实际上是在编程在一个非常复杂的操作系统中允许发生什么交互作用,比如 Linux,在那里交互作用本身通常是复杂的。就像编程语言一样,你可以用不同的风格和方法做事情,产生不同的结果。也许在那个程序中使用一个switch()会使它比一个else-if块更清晰、更容易理解,尽管在功能上你最终会得到同样的东西。SELinux 也是一样;您通常可以使用强制机制的一部分来完成事情,而使用替代机制会更合适。在后面的章节中,我们将介绍标记目标和主题的过程,这是系统中比较困难的部分之一。

当有人编写程序时,他们通常有一套软件应该执行的要求。这些都是软件的要求。在 SELinux 中,你应该做同样的事情。您应该收集安全需求并了解您希望保护自己免受的威胁模型。一个设计良好的 SELinux 策略将满足这些目标。一个伟大的设计会以一种易于扩展的方式来实现它。这最终是谨慎和明智地使用 UBAC、RBAC、TE 和 MLS 的组合来帮助实现需求和设计目标的地方。

总结

在本章中,我们介绍了 SELinux 的主要工作部分,包括类型强制、多级和多类别安全性,以及用户和角色。此外,我们看到了如何应用这些技术来实现日益复杂的访问策略,这是一个具体的例子。在下一章中,我们将移出内核,并发现安卓如何在其非常独特的用户空间中工作。