Discretion will protect you, and understanding will guard you. | |
Proverbs 2:11 (NIV) |
在讨论如何应用Linux或Unix安全特性指南前,先从一个程序员的角度了解一下这些特性是很有用的。本节对这些在几乎所有类Unix系统上广泛应用的特性进行简要的描述。尽管如此,还是需要注意类Unix系统不同版本间相当大的差异,以及不是所有系统都具有这里所描述的能力。本节也着重提到了Linux特有的一些扩展或特性;从安全编程的角度来看,不同Linux发行版非常相似,因为它们本质上都使用相同的内核与C库(以及鼓励任何创新迅速传播的基于GPL的许可)。本文不讨论很多类Unix系统都不实现的强制存取控制(MAC)的实现之类的问题。如果你已经知道了这些特性,可以跳过本节继续阅读。
很多编程指南简单地略过了Linux或Unix有关安全的部分,而且忽略了重要的信息。特别是它们经常讨论通常情况下“如何应用”某物,而不考虑影响这些应用的安全属性。与此相反,在单个函数的手册页中有大量的详细信息,但是有时手册页对于如何使用每个单独函数的详细讨论模糊了关键的安全性问题。本节试图弥补此缺憾;只为程序员提供Linux下可能会用到的安全机制的全局概貌,但特别注重有关安全问题的分支。本节比经典的编程指南更为深入,特别是集中在有关安全的问题上,并指出从哪里可以获得进一步的资料。
首先,是基本情况。 Linux和Unix从根本上来说可以分为两部分:内核与“用户空间”。绝大多数程序运行在用户空间(在内核之上)。Linux支持“内核模块”的概念,简单地说就是在内核里动态载入代码的能力,但还是有这样的基本划分。有些其它的系统(如HURD)是基于“微内核”的系统;它们有一个功能很有限的小内核和一组“用户”程序来实现传统上由内核实现的底层函数。
有些类Unix系统进行了大量修改以支持增强的安全性,特别是支持美国国防部对强制存取控制(B1级别以上)。本文的目前版本不涉及这些系统或这些问题;我希望在未来的版本中可以加入这些内容。
当用户登录时,他们的用户名被映射为整数,来标明自己的“UID”(用户ID)和作为其中成员的“GID”(组ID)。UID 0是传统上被称为“root”的具有特权的用户(角色),在绝大多数类Unix系统(包括Unix)中root可以强制变更大多数的安全性检查,被用来管理系统。就安全性而言,进程是唯一的“主题”(也就是说,只有进程是活动的目标)。进程可以访问各种数据对象,特别是文件系统对象(FSO)、系统V进程间通信(IPC)对象和网络端口。进程还可以设置信号。其它有关安全的主题包括配额与限制、库、审核和PAM。以下几节对此进行详细说明。
在类Unix系统上,用户级别的活动由运行的进程来实现。绝大多数Unix系统支持作为独立概念的“线程”;一个进程内的线程共享内存,而且系统的调度器实际上是调度线程。Linux的做法与此不同(依我看是做得更好):线程与进程没有本质的差异。在Linux下,在某个进程创建另一个进程时,它可以选择共享哪些资源(比如内存可以共享)。随后Linux内核会进行优化以获得线程级的速度;参见clone(2)以了解更多信息。值得注意的是Linux内核的开发者倾向于使用“任务”一词,而不是“线程”或“进程”,但外界的文档则倾向于使用进程一词(所以我在文中如此使用)。在多线程应用程序编程时,使用某个标准的线程库来隐藏这些差异通常要好一些。这不仅使线程更易于移植,而且有些库通过把多个应用程序级的线程实现为单个操作系统线程的方法提供一个间接的附加级别;这可以改进某些系统上一些应用程序的性能。
在类Unix系统中,每个进程所有的典型属性如下:
RUID, RGID -- 运行进程的用户的真实用户ID和组ID
EUID, EGID -- 用于权限检查(文件系统除外)的有效用户ID和组ID
SUID, SGID -- 保存的用户ID和组ID;用来支持下面要讨论的切换许可“开和关”。不是所有的类Unix系统都支持它。
补充群组 -- 用户有成员资格的群组(GID)列表。
umask -- 在创建一个新的文件系统对象时决定缺省访问控制设置的一组比特位;参见umask(2)。
scheduling parameters -- 每个进程都有一个调度策略,缺省策略为SCHED_OTHER的进程还具有nice、priority和counter的附加属性。参见sched_setscheduler(2)以了解更多信息。
limits -- 每个进程的资源限制(参见下文)。
filesystem root -- 进程角度的根文件系统起始处;参见see chroot(2)。
下面是与进程有关的不太普通的属性:
FSUID, FSGID -- 用于文件系统访问检查的用户ID和组ID;一般等于相应的EUID和EGID;这是一个Linux特有的属性。
capabilities -- POSIX能力信息;一个进程实际上有三组能力:有效的、可继承的和许可的能力。参见下文中有关POSIX能力的更多信息。版本2.2以上的Linux内核支持这一点;有些其它的类Unix系统也支持,但不够普遍。
在Linux下,如果确实需要了解哪些属性与每个进程相关,最可靠的信息源是Linux源码,特别是/usr/include/linux/sched.h中的task_struct定义。
创建新进程的可移植方式是使用fork(2)调用。BSD作为优化技术引进了一个叫做vfork(2)的变种。vfork(2)的使用原则很简单:如果可以避免就不要使用它。vfork(2)与fork(2)不同,在调用execve(2V)或退出之前,子进程借用父进程的内存和控制线程;在子进程其资源时,父进程被悬挂。其原理是在旧的BSD系统中,fork(2)实际上会导致内存复制,而vfork(2)则不会。Linux则根本不会出现这个问题;因为Linux内部采用写时复制的语义,只有在改变时才复制内存页(实际上Linux还是有些表要复制的;在绝大多数情况下由此带来的负荷不大)。尽管如此,由于有些程序依赖于vfork(2),最近Linux实现了BSD的vfork(2)语义(以前Linux下的vfork(2)只是fork(2)的别名)。vfork(2)的问题在于,进程要想不与其父进程互相干扰需要相当的技巧,特别是使用高级语言。其后果在于:一旦代码改变,甚或编译器版本变化,都会很容易使调用了vfork(2)的程序失效。在绝大多数情况下应该避免vfork(2);它的主要用途在于支持需要vfork语义的老程序。
Linux支持Linux特有的clone(2)调用。该调用与fork(2)类似,但允许明确说明哪些资源可以共享(如内存、文件描述符等等)。可移植程序不应该直接使用此调用;而是应该象前面所说的那样,依赖于使用该调用实现线程的线程库。
本文不是编写程序的完全手册,所以将跳过大量存在的处理进程的信息。可以参见wait(2)、exit(2)一类的文档以了解更多内容。
POSIX能力是支持把通常由root拥有的特权分割为更多更专门特权的一组比特位组合。POSIX能力是由一个IEEE标准草案定义的;它不是Linux所独有的,但也并非其它类Unix系统普遍支持的。Linux内核2.0不支持POSIX能力,版本2.2增加了对进程的POSIX能力的支持。当Linux文档(包括本文)中提到“要求root权限”时,实际上几乎都是意味着像能力文档中所说的那样“要求某个能力”。如果想知道要求的特定能力,请在能力文档中进行查找。
在Linux中,其最终目的是允许能力与文件系统中的文件联系起来;但到本文档完成时,Linux还不支持这一点。Linux对能力传递有支持,但缺省情况下被禁用。版本2.2.11的Linux增加了一个叫做“能力绑定设置”的特性,使能力的应用更直接更有用。能力绑定设置是一组允许被系统中任意进程所拥有的能力(否则,只有特殊的初始化进程可以拥有这些能力)。如果某能力不在此绑定设置中,则无论有没有权限,都不可以被任意进程所使用。例如,此特性可用来禁止内核模块加载。利用此特性的一个工具实例是LCAP http://pweb.netcom.com/~spoon/lcap/。
更多有关POSIX能力的资料可以从 ftp://linux.kernel.org/pub/linux/libs/security/linux-privs 获得。
进程可以用fork(2)、不推荐使用的vfork(2)或者Linux独有的clone(2)来创建;这些系统调用都复制当前进程,并从中创建两个进程。一个进程可以通过调用execve(2)、或它的各种前端(参见exec(3)、system(3)和popen(3))来执行一个不同的程序。
在程序执行时,其文件设置自己的setuid或setgid比特位,进程的EUID或EGID(分别)被设置为文件的EUID或EGID值。在用来支持setuid或setgid脚本时,由于存在竞争状态,此功能会导致一个老的UNIX安全漏洞。在内核打开文件来查看运行的解释器和(正在设置ID的)解释器回转并重新打开文件以解释文件之间,攻击者可以改变文件(直接或通过符号连接)。
不同的类Unix系统采用不同的方法处理setuid脚本的安全问题。某些系统,如Linux,在执行脚本时完全忽略setuid和setgid比特位,这显然是一个安全的措施。SysVr4和BSD 4.4的大多数现代发行版使用一种不同的方法来避免内核竞争状态。在这些系统中,当内核把要打开的setuid脚本的名称传递给解释器时,不使用路径名(这会允许竞争状态),而是传递文件名/dev/fd/3。这是一个脚本已经打开的特殊文件,所以不会出现攻击者可以利用的竞争状态。即使在这些系统上,我依然建议不要在安全程序中使用setuid/setgid脚本编程语言,下面会进一步讨论这个问题。
在某些情况下,进程会影响各种UID和GID的值;参见setuid(2)、seteuid(2)、setreuid(2)和Linux特有的setfsuid(2)。特别是保存的用户ID(SUID)属性允许可信任的程序临时切换自己的UID。类Unix系统支持按以下规则使用SUID:如果RUID被改变,或者EUID被设置为不等于RUID的值,SUID就被设为新的EUID。非特权用户可以用自己的SUID来设置EUID,把RUID设为EUID,以及把EUID设为RUID。
Linux特有的进程属性FSUID是用来允许NFS服务器一类的程序把自己的文件系统权限限制在某些给定的UID上,而不给这些UID向进程发送信号的许可。一旦EUID被改变,FSUID就被改为新的EUID值;FSUID的值可以用Linux独有的调用setfsuid(2)单独进行设置。注意,非root调用者只能把FSUID设置为当前的RUID、EUID、SEUID或当前的FSUID。