为什么会有 setuid?为什么不是别的机制?

2012-07-16

来我们组面试过的同学,如果在简历中写了“熟悉 Unix/Linux”之类的话,那么很可能被问到类似这样的问题:“为什么会有 setuid?为什么不是别的机制?”。前不久和徐老师讨论设计原则问题时又聊到了这个 setuid。应徐老师建议,写下这篇命题作文,说说我的理解。

首先从 setuid 的应用场景谈起。很多教科书和网络文章讲 setuid 时都会拿 passwd 命令举例(据说老一辈人更喜欢举 mailbox 的例子,异曲同工)。我们抛开对已知实现方式的理解,假设需要重新设计,看看这个场景有哪些需求和解决方案。需求方面,一种典型理解是:要求用户 a 可以使用程序 φ 受控地操作用户 b 的文件 θ。所谓受控,是指满足一个操作约束集合 S。除此之外,用户 a 对用户 b 的其他文件没有任何特权。事实上这就是一个访问控制问题,前人已经总结了若干解决模型。完备的角色访问控制(RBAC)或许能够涵盖全部需求,只要系统或管理员为特定的业务提供特定的角色并严格控制会话;即便使用自主访问控制(DAC),只要实现得足够细粒度,也可以尽力满足上述需求,只是安全性保证略差。但以文件为主要客体抽象的 Unix 偏偏选择了一种粗粒度的 DAC——ugorwx。为什么要做出这种选择?这很可能与 Unix 的“简单(K.I.S.S.)”设计哲学密切相关。相比其他模型,ugorwx 对系统和应用作出最少的必要假设,在提供最基本安全性的同时保证了较小的存储和决策开销。单纯使用 ugorwx 模型已经能够涵盖大多数正常业务操作(与系统管理或实用工具操作相对)的访问控制共性需求。

然而 ugorwx 不能满足 passwd 和 mailbox 那样的需求,因为它对 θ 的控制粒度太粗,允许 a 使用 φ 操作 θ,就意味着必须允许所有用户使用任意程序操作 θ,那么篡改他人密码、偷看他人邮件岂不易如反掌?于是,必须要有一种额外机制为 ugorwx 的简单性承担责任。现在假设要我们来设计这个额外机制,可以怎么干呢?万全之道可能是做一套基于规则的访问控制系统,在特定条件触发时执行——这显然不够简单。还可以给每个用户或文件挂一个访问控制列表(ACL),声明特权,但其实主体之间的特殊关系可以通过 group 约束,ACL 这种特殊化不但不够简单,还可能违背了 ugorwx 的设计初衷。那么,只能把目光集中到那个特殊的程序 φ 上。可以对它做一些手脚吗?其一,φ 只是一小类需要特殊处理的例外程序,程序本身是可以精心设计的,也就是说 φ 已经自包含了 S 和 θ 属性,无须对 S 和 θ 做专门处理。其二,既然 ugorwx 的设计将主体关系特殊性交由 group 处理,那么就有理由忽略各个 others 成员的特殊性,即忽略 a。其三,b 对 θ 的 owner 关系是 ugorwx 中已经存在的,φ 运行时访问 θ,可以获知这个信息。基于上述事实,一种最小开销的额外机制设计也就水到渠成:通过一个特殊的标志位标识特殊的程序 φ,并在进程中区分“真实 UID”与“有效 UID”的概念——这基本就是 setuid 了。论功能,setuid 的确不够完备,但上述分析说明这并不是大问题,至少已经可以应对 passwd 和 mailbox 场景了。

事实上,Unix 设计哲学对各个属性的取舍早已得到总结。Richard Gabriel 的经典名篇 The Rise of “Worse is Better”中将其归纳为“新泽西方法”,即:简单性是系统设计的第一要素,实现的简单比接口的简单更重要;任何值得注意的方面都要求正确性,但为了简单性,正确性可以轻微让步;设计不能过于不一致,但为了简单性,一致性可以有所牺牲;完备性应该覆盖实践中许多重要的情况,但只要简单性受到危害,完备性必须作出牺牲。对照来看,ugorwx 是一种接口和实现都很简单的设计;ugorwx 与 setuid 提供的安全级别(正确性)有限,但对通用操作系统来说已经足够;setuid 以牺牲接口一致性为代价确保了 ugorwx 机制在多数情况下的简单性,同时本身的实现也保持了尽量简单;setuid 不具有 RBAC 那样的完备性,但已经能够应对系统管理或实用工具的等常见的重要场景。

最后,我们向历史求证一下 setuid 的来源。Wikipedia 给出的答案并不令人惊讶,setuid 的创造者正是 Dennis Ritchie 大神,当时的一篇专利(US4135240 - Protection of data file contents)记录了他的设计思想。在这篇如同学术论文的专利中,Ritchie 确实把简单性作为了 setuid 的主要出发点和创新点。专利风格诚恳,不但给出了 setuid 的设计方案和应用示例,同时也承认了它在功能上的不足。根据 Wikipedia 上给出的引文,AT&T 出于“不值得收取许可费用”的考虑,将该专利释放到了公有领域,任由开发者使用,这也使得 setuid 在多种衍生系统中发扬光大,流传至今。

当然,对于这种开放性问题,每个人都可能有自己的答案,本文观点也只是作者的个人理解。不过如果你下次面试时遇到了诸如“为什么会有 ioctl”之类的问题,不妨从设计哲学角度考虑一下。