在类 Unix 系统中,用户级别的活动通过运行进程来实现。大多数 Unix 系统支持将“线程”作为一个独立的概念;线程在进程内部共享内存,并且系统调度器实际上调度的是线程。Linux 对此有不同的处理方式(并且我个人认为使用了更好的方法):线程和进程之间没有本质的区别。相反,在 Linux 中,当一个进程创建另一个进程时,它可以选择共享哪些资源(例如,可以共享内存)。然后,Linux 内核执行优化以获得线程级别的速度;有关更多信息,请参阅 clone(2)。值得注意的是,Linux 内核开发人员倾向于使用“任务”这个词,而不是“线程”或“进程”,但外部文档倾向于使用进程这个词(因此我在这里使用术语“进程”)。在编写多线程应用程序时,通常最好使用标准的线程库之一来隐藏这些差异。这不仅使线程更具可移植性,而且一些库还提供了一个额外的间接层,通过将多个应用程序级线程实现为单个操作系统线程;这可以为某些应用程序在某些系统上提供一些性能改进。
以下是与类 Unix 系统中每个进程关联的典型属性
RUID, RGID - 运行进程的用户的实际 UID 和 GID
EUID, EGID - 用于特权检查的有效 UID 和 GID(文件系统除外)
SUID, SGID - 保存的 UID 和 GID;用于支持如下所述的“开启和关闭”权限切换。并非所有类 Unix 系统都支持这一点,但绝大多数都支持(包括 Linux 和 Solaris);如果您想检查给定的系统是否在 POSIX 标准中实现了此选项,您可以使用 sysconf(2) 来确定 _POSIX_SAVED_IDS 是否生效。
补充组 - 此用户所属的组(GID)列表。在最初的版本 7 Unix 中,这并不存在 - 进程一次只属于一个组,并且必须执行一个特殊的命令来更改该组。BSD 为每个进程添加了对组列表的支持,这更加灵活,并且这种添加现在已被广泛实现(包括 Linux 和 Solaris)。
umask - 一组位,用于确定创建新文件系统对象时的默认访问控制设置;请参阅 umask(2)。
调度参数 - 每个进程都有一个调度策略,而那些具有默认策略 SCHED_OTHER 的进程还具有额外的参数 nice、优先级和计数器。有关更多信息,请参阅 sched_setscheduler(2)。
限制 - 每个进程的资源限制(见下文)。
文件系统根目录 - 进程对根文件系统(“/”)开始位置的理解;请参阅 chroot(2)。
以下是与进程关联的较不常见的属性
FSUID, FSGID - 用于文件系统访问检查的 UID 和 GID;这通常分别等于 EUID 和 EGID。这是 Linux 独有的属性。
capabilities(能力) - POSIX 能力信息;进程实际上有三组能力:有效能力、可继承能力和允许能力。有关 POSIX 能力的更多信息,请参见下文。Linux 内核版本 2.2 及更高版本支持此功能;其他一些类 Unix 系统也支持,但它并不像 Linux 那样普及。
在 Linux 中,如果您真的需要确切知道与每个进程关联的属性,最权威的来源是 Linux 源代码,特别是/usr/include/linux/sched.h中 task_struct 的定义。
创建新进程的可移植方法是使用 fork(2) 调用。BSD 引入了一个称为 vfork(2) 的变体作为一种优化技术。关于 vfork(2) 的底线很简单:如果可以避免,不要使用它。有关更多信息,请参阅第 8.6 节。
Linux 支持 Linux 独有的 clone(2) 调用。此调用类似于 fork(2),但允许指定应共享哪些资源(例如,内存、文件描述符等)。各种 BSD 系统实现了 rfork() 系统调用(最初在 Plan9 中开发);它具有不同的语义,但总体思路相同(它也创建了一个对共享内容具有更严格控制的进程)。可移植程序应尽可能避免直接使用这些调用;如前所述,它们应改为依赖于使用此类调用来实现线程的线程库。
本书不是关于编写程序的完整教程,因此我将跳过关于处理进程的广泛可用的信息。您可以查看 wait(2)、exit(2) 等的文档以获取更多信息。
POSIX 能力是位集,允许将通常由 root 用户持有的特权拆分为更大、更具体的特权集。POSIX 能力由 IEEE 标准草案定义;它们并非 Linux 独有,但并非所有其他类 Unix 系统都普遍支持。Linux 内核 2.0 不支持 POSIX 能力,而版本 2.2 为进程添加了对 POSIX 能力的支持。当 Linux 文档(包括本文档)说“需要 root 权限”时,在几乎所有情况下,它实际上是指“需要 capability(能力)”,如 capability 文档中所述。如果您需要知道所需的特定 capability,请在 capability 文档中查找。
在 Linux 中,最终目的是允许将 capabilities 附加到文件系统中的文件;但是,截至本文撰写之时,尚不支持此功能。对传输 capabilities 的支持,但默认情况下禁用此功能。Linux 版本 2.2.11 添加了一项功能,使 capabilities 更加直接有用,称为“capability bounding set”(能力边界集)。capability bounding set 是一个 capabilities 列表,允许系统上的任何进程持有这些 capabilities(否则,只有特殊的 init 进程才能持有它)。如果某个 capability 未出现在 bounding set 中,则任何进程都不能行使它,无论权限有多高。此功能可用于例如禁用内核模块加载。一个利用此功能的示例工具是 LCAP,网址为 http://pweb.netcom.com/~spoon/lcap/。
有关 POSIX capabilities 的更多信息,请访问 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(分别地)通常设置为文件的值。当用于支持 setuid 或 setgid 脚本时,此功能是旧的 Unix 安全漏洞的来源,这是由于竞争条件引起的。在内核打开文件以查看要运行哪个解释器的时间,以及(现在是 set-id)解释器转过身并重新打开文件以解释它之间,攻击者可能会更改文件(直接或通过符号链接)。
不同的类 Unix 系统以不同的方式处理 setuid 脚本的安全问题。某些系统(如 Linux)在执行脚本时完全忽略 setuid 和 setgid 位,这显然是一种安全的方法。SysVr4 和 BSD 4.4 的大多数现代版本使用不同的方法来避免内核竞争条件。在这些系统上,当内核将 set-id 脚本的名称传递给解释器打开时,它不是使用路径名(这将允许竞争条件),而是传递文件名 /dev/fd/3。这是一个已在脚本上打开的特殊文件,因此攻击者无法利用竞争条件。即使在这些系统上,我也不建议将 setuid/setgid shell 脚本语言用于安全程序,如下所述。
在某些情况下,进程可以影响各种 UID 和 GID 值;请参阅 setuid(2)、seteuid(2)、setreuid(2) 和 Linux 独有的 setfsuid(2)。特别是,保存的用户 ID (SUID) 属性用于允许受信任的程序临时切换 UID。支持 SUID 的类 Unix 系统使用以下规则:如果 RUID 被更改,或者 EUID 被设置为不等于 RUID 的值,则 SUID 将被设置为新的 EUID。非特权用户可以将其 EUID 从其 SUID 设置,将 RUID 设置为 EUID,并将 EUID 设置为 RUID。
Linux 独有的 FSUID 进程属性旨在允许像 NFS 服务器这样的程序将其自身限制为仅具有给定 UID 的文件系统权限,而不授予该 UID 向进程发送信号的权限。每当 EUID 更改时,FSUID 都会更改为新的 EUID 值;可以使用 setfsuid(2)(Linux 独有的调用)单独设置 FSUID 值。请注意,非 root 调用者只能将 FSUID 设置为当前的 RUID、EUID、SEUID 或当前的 FSUID 值。