Linux访问控制模型和进程凭证

Linux的访问控制模型

Linux传统的访问控制模型是DAC(Discretionary Access Control,自主访问控制)。DAC Model是根据自主访问控制策略建立的一种模型,允许合法用户以用户或用户组的身份访问策略规定的客体,同时阻止非授权用户访问客体,某些用户还可以自主地把自己所拥有的客体的访问权限授予其他用户。在Linux中,这里用户和用户组就对应了user和group,客体就代表了文件、文件夹、IPC等共享资源。对于客体,比如文件来说,可以对于不同的主体分别设置rwx权限。当然,对于主体的划分粒度较粗,只能对文件所有者、同组用户、其他用户分别设置,没法针对每个用户单独设立权限。

ps:SELinux上引入了MAC模型,这里不做深入。

进程的用户ID凭证

众所周知,Linux上一切操作都是基于进程来进行,比如常见的在shell里执行命令等。在执行需要权限判断的操作时,进程都会通过某个系统调用陷入内核,由内核来进行权限的判断。那么很自然就能想到,既然Linux的DAC模型基于用户和组做权限控制,那么进程里必然得保存关于用户和组的信息。具体实现上来说,进程都有一套数字来表示它所属于的用户ID和组ID。以下主要讲解用户ID凭证,组ID的原理和实现和用户类似,就不再赘述。这些ID称为进程凭证。对于用户ID来说,具体有三个:

  • 实际用户ID(real user ID)
  • 有效用户ID(effective user ID)
  • 保存的set-user-ID(saved-user-ID)

需要保存三个uid吗?

一个ID够吗?

可能有人会困惑,为什么需要保存三个id。只保存一个启动进程的用户ID可不可行呢?当前用户通过login进程登录之后,保存它的uid。后续再由该用户启动的程序都是login进程的子孙进程,只要让子进程的uid凭证都继承自父进程,uid就此就能保存下来。

以上的设计在大部分场景下够用。但是有些程序的权限需求比较特殊,得让普通用户执行也有文件所有者的权限。比如说用户的密码储存在/etc/shadow中,普通用户不可读写。但是,passwd程序允许用户修改它们自己的密码。也就是,当用户执行passwd,它们可以突然修改/etc/shadow,而且得识别出启动进程的用户,如何实现?如果按照上述的设计,普通用户执行passwd,进程uid为非0,那必然没有/etc/shadow的读写权限。

set-uid,两个ID够吗?

于是,早年的开发者们就想到了,在文件的属性上加了一位做标记,set-user-id位。那么,继续沿用上述的设计,在exec标志set-user-id位的可执行程序时,将进程的uid改成文件所有者,普通用户无法读写/etc/shadow问题就迎刃而解了。但这样引入了了另一个问题,诸如passwd这样的程序无法知道启动进程的用户,都不知道该改哪个用户的密码了。很显然,进程保存一个uid肯定是不够用了,至少得再加一个。一个记录运行程序的用户id,一个记录实际用于权限判断的用户id。实际上,real-uideffective-uid就是干的这个事情。real-uid为启动进程的用户id,effective-uid为实际用户权限判断的用户id。大部分情况下,real-uideffective-uid相同。运行设置set-uid的程序,effective-uid会改成程序文件的owner。

这样的设计也不够好,因为effective-uid的更改变成了一锤子买卖。如果有进程需要在启动的用户和文件owner之间反复横跳怎么办?effective-uid改回real-uid之后文件所属用户id就丢失了(进程得根据执行的文件exe大费周折去文件系统的inode里查所属用户)。

最小权限原则,三个ID

这个”反复横跳”的需求也是很有必要的。有个最小权限原则(最早由 Saltzer 和 Schroeder 提出):

每个程序和系统用户都应该具有完成任务所必需的最小权限集合。
限制代码运行所需的安全权限,有一个非常重要的原因,就是降低你的代码在被恶意用户利用时,造成的损失。如果你的代码仅仅使用最小权限来执行,恶意用户就难以使用它造成损失。如果你需要用户使用管理员权限来执行代码,任何代码中的安全缺陷,都会通过利用该缺陷的恶意用户,潜在造成更大的损失。

根据最小权限原则,只有实际进行关键操作的时候获取权限,其余时候应该禁用。比如说对于passwd程序来说,最好就是只有在读写/etc/shadow的时候获取root权限,其余时候(比如说等待用户输入时)放弃特权。

具体规则

所以,最后就形成了现今的实现,进程里保存了三个uid。这三个uid初始化的规则如下:

  1. real-uid为启动进程的用户。
  2. 如果是set-uid程序运行的进程,effective-uid为文件的所有者;否则和real-uid相同,为启动进程的用户。
  3. saved-uideffective-uid复制而来。

对于普通的非特权用户来说,允许通过一些系统调用让effective-uidreal-uidsaved-uid之间来回变动。进程运行时,权限的检查则都是基于effective uid。对于一个拥有良好安全设计的set-uid程序来说,只有需要使用特殊权限的时候才把effective-uid切换成文件所有者,其余时候都应该为进程启动者。

系统接口

Linux上支持改动修改进程凭证的一些系统接口:

修改进程凭证的一些系统调用

参考

  1. 《Linux系统编程手册》第9章
  2. https://wizardforcel.gitbooks.io/syracuse-sec-lecture-notes/content/3.html