当前位置:嗨网首页>书籍在线阅读

19-不具备文件能力的老式内核和系统

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

39.10 不具备文件能力的老式内核和系统

本节将介绍之前各种版本的内核中有关能力实现方面的差异以及碰到不支持文件能力的内核时所发生的行为差异。Linux在下面两个场景中是不支持文件能力的。

  • 在Linux 2.6.24之前的版本中没有实现文件能力。
  • 自Linux 2.6.24起,当在构建内核时不指定CONFIG_SECURITY_FILE_CAPABILITIES选项时文件能力将会被禁用。

虽然从2.2内核开始,Linux就已经引入了能力并允许将能力附加到进程中,但文件能力的实现则推后了好几年。之所以未实现文件能力的原因不是因为技术上的困难,而是因为政策的原因。(第16章介绍的扩展特性被用来实现文件能力,但它直到2.6内核才可用。)大部分内核开发人员要求系统管理员为各个特权程序分别设置和监控能力集的意见会使得管理任务变得复杂和难以管理——虽然有些意见是合理的,但很难做到。相反,系统管理员对于现有的UNIX权限模型比较熟悉,他们知道如何小心处理set-user-ID程序并且能够使用简单的find命令找出系统中的set-user-ID和set-group-ID程序。不过,文件能力模块的开发人员使得文件能力的应用在管理上变得可行,最终为将文件能力集成进内核提供了足够的令人信服的论据。

CAP_SETPCAP能力

在不支持文件能力的内核中(即所有2.6.24之前的内核以及自2.6.24起文件能力被禁用的内核),CAP_SETPCAP能力的语义是不同的。根据与39.7节中描述的规则类似的规则,从理论上来讲,一个在有效集中包含CAP_SETPCAP能力的进程能够修改除自身之外的其他进程的能力。换句话说,可以修改另一个进程的能力、指定进程组中所有成员的能力以及系统中除init和调用者本身之外的所有进程的能力。之所以将init排除在外是因为它对于系统的运作起着基础性的作用。之所以还将调用者本身排除在外是因为调用者可能会试图删除系统中其他进程的能力,但这里并不希望调用进程能删除自己的能力。

修改其他进程的能力只是在理论上可行,在较早的内核以及禁用了文件能力的现代内核中,能力边界集(稍后讨论)总是会隐藏掉CAP_SETPCAP能力。

能力边界集

自Linux 2.6.25起,能力边界集就是一个进程级的特性了。但在较早的内核中,能力边界集是一个系统级别的特性,它会影响系统中的所有进程。在初始化系统级别的能力边界集时总是会隐藏CAP_SETPCAP(参见前面的介绍)。

在2.6.25之后的内核中,只有当在内核中启用了文件能力时才支持从各个进程的边界集中删除能力。在那种情况下,所有进程的祖先进程init在启动的时候会包含所有的能力,系统中所有其他进程会继承该边界集的一个副本。如果文件能力被禁用了,那么由于上面描述的CAP_SETPCAP能力的语义存在差别,因此init在启动的时候会包含除CAP_SETPCAP之外的所有能力。

在Linux 2.6.25中对能力边界集的语义还做了另一个变更。在之前(39.5.1节)曾经提过,在Linux 2.6.25以及之后的版本中,各个进程的能力边界集是作为能够被添加到进程的可继承集中的能力的一个受限超集来处理的。在Linux 2.6.24以及之前的版本中,系统级别的能力边界集并没有这种掩码效果(不需要这种效果,因为这些内核不支持文件能力)。

通过Linux特有的/proc/sys/kernel/cap-bound文件能够访问系统级别的能力边界集。进程必须要具备CAP_SYS_MODULE能力才能修改cap-bound文件的内容。但只有init进程才能够开启这个掩码中的位,其他特权进程只能关闭掩码中的位。这些限制的结果就是在不支持文件能力的系统上永远都无法将CAP_SETPCAP能力赋给进程。这种做法是合理的,因为这个能力可以用来破坏整个内核权限检查系统。(当需要修改这个限制时必须要加载一个修改集合中的值的内核模块并修改init程序的源代码或者在内核源代码中修改能力边界集的初始化过程并重建内核。)

令人迷惑的是,虽然是一个位掩码,但在cap-bound文件中系统级别的掩码值显示为一个带符号的十进制数字。如文件中的初始值是−257,它是除(1 << 8)之外所有位都被开启的位掩码的二的补码表示(即二进制格式为11111111 11111111 11111110 11111111);CAP_SETPCAP的值为8。

在运行于无文件能力的系统上的程序中使用能力

即使在不支持文件能力的系统上仍然可以使用能力来提升程序的安全性。这是通过下列步骤来完成的。

1. 在一个有效用户ID为0的进程中运行这个程序(通常是一个set-user-ID-root程序)。此类进程的许可和有效能力集中包含了所有的能力(前面提过,除了CAP_SETPCAP能力)。

2. 在程序启动的时候使用libcap API删除有效集中的所有能力和许可集中除后面会用到的能力之外的其他所有能力。

3. 设置SECBIT_KEEP_CAPS标记(或使用prctl() PR_SET_KEEPCAPS操作达到同样的效果),这样在下一步中就不会删除能力了。

4. 将所有用户ID设为非0值以防止进程访问由root拥有的文件或在exec()中获取能力。

如果需要防止进程在exec()中重新获取权限但同时要允许它访问由root拥有的文件的话则可以使用SECBIT_NOROOT标记这一步来取代前面的两步。(当然,允许进程访问由root拥有的文件为一些安全性风险打开了大门。)

5. 在程序的后续生命周期中根据需要使用libcap API在有效集中提升或删除剩余的许可能力以便执行特权任务。

一些基于2.6.24之前的Linux内核的应用程序采用了这种方法。

在所有反对为可执行文件实现能力的内核开发者所提出的反对理由中,反对使用正文中所描述的方法的最充分的理由之一是应用程序的开发人员通常知道可执行程序需要用到哪些能力,而系统管理员可能无法轻易地确定此类信息。