LWN:符号链接带来的麻烦!

关注了就能看到更多这么棒的文章哦~

The trouble with symbolic links

July 7, 2022
This article was contributed by Chris Riddoch
DeepL assisted translation
https://lwn.net/Articles/899543/

在 2022 年的 sambaXP 会议上,Jeremy Allison 做了一个题为 "UNIX 文件系统 API 被严重破坏,该怎么办?"。LWN 的常客们可能记得在最近的一个 LWN 文章评论区讨论中有提到过这些讲座。他从符号链接("symlinks")给应用程序开发人员带来的问题开始讲起,然后讨论了为了解决符号链接带来的问题所引入的方法如何导致了路径名处理时涉及的 API 的复杂性大幅增加。

Allison 解释说,硬链接(hard link)是原始 Unix 文件系统 API 的第一个 "有趣的新增功能";但与符号链接不同,它们并不危险,而且事实上很容易使用。硬链接只是一个 directory entry 跟该 entry 所指向的文件(或目录)的 inode 之间的关联关系。Unix 系统允许对任何文件进行多重 link,但要求 inode 和 directory entry 都位于同一个文件系统中。

而符号链接则包含的是另一个路径作为内容的,当系统调用 open()或 chown()时,内核会直接沿着该路径上操作文件,这是一个对用户透明的操作。这个看似无害的功能导致了复杂性出现难以置信的增长,从而满足那些需要知道一个路径名中是否包含了符号链接的程序。这包括 tar 等归档程序、rsync 等文件同步和传输程序、Samba 等网络文件系统服务器,以及更多由于没有充分注意路径名中的符号链接而导致安全问题的程序。

在 CVE 历史进行一下搜索,就可以看出符号链接导致过的各种安全问题,当 Allison 进行这个搜索时,他得到了 1361 条结果。这些漏洞包括泄露信息、获取特权、以及包括删除在内的对文件进行任意操作,以及其他一些攻击。他没有详细讨论具体的某个 CVE,而是举了一个例子,说明与符号链接有关的漏洞可能导致的安全问题。

一个以 root 身份运行的程序在打开文件 /data/mydir/passwd 之前,可能会尝试检查 /data/mydir 是一个普通的目录(不是一个符号链接)。在程序进行目录检查之后,尚未来得及打开文件之前,攻击者可以用指向 /etc 的符号链接来替换掉 mydir 目录,现在打开的文件就变成了 /etc/passwd。这是一种被称为 "检查时间到使用时间差"(TOCTOU)的竞态攻击。

Symlinks and complexity

Allison 认为,符号链接的产生原因,是由于硬链接被限制了只能在同一文件系统内进行 link,所以如果管理员想在不改变用户数据路径的情况下来增加新的存储设备,就只有使用符号链接了(它没有这种限制)。他引用了 4.2BSD 的一个宣传广告,其中说:"这个功能使用户摆脱了树状结构所带来的严格的层次结构的限制。这种灵活性对于更好地对 namespace 进行管理是至关重要的。"

增加了符号链接,就引出了 lstat()系统调用,这个系统调用是用来识别路径名中最后一个字段是否为符号链接的方式。不幸的是,对于处理指向路径中早期目录的符号链接来说还是不够的。应用程序可以尝试单独检查路径中的每个字段,但这个操作不是原子性的,因此另一个应用程序可以在这个过程中对其中的一个字段进行修改,从而导致安全漏洞的出现。

open()系统调用有一个选项 O_NOFOLLOW,也同样表现出了相同的问题。O_NOFOLLOW 告知这个系统调用,如果路径名中的最后一个字段是一个符号链接,那么就返回 ELOOP 错误,但它只检查了最后一个字段。Realpath() 这个 C 库函数会跟踪路径中的符号链接,并产生一个绝对的、规范的路径名,然后应用程序可以与原始路径进行比较。Allison 说这是一个吸引人的解决方案,但不是正确的方案。在调用 realpath() 之后,以及使用另一个函数来操作文件之前,这个时间窗里可能有另一个进程来进行改动。换句话说,这里同样出现了 TOCTOU 竞争。

Allison 说,openat() 系统调用被设计出来,希望对这个问题提供解决方案;它引入了相对于一个由已经打开的文件描述符所指示的目录来引用文件的方案。确定文件路径的唯一可靠方法是通过多次调用 openat() 来逐一访问整个路径层次。其他一切方案都会受到上面这个竞争问题的影响。

但是 Allison 也指出了这种技术的缺陷。"你不能用 open() 创建一个新的目录,你不能用 open()调用来删除一个文件,对一个文件进行 unlink,或删除一个目录。" 因此,必须创建更多遵循 openat() 模式的函数,比如 mkdirat()、linkat()、unlinkat()、renameat() 等等。有些函数仍然缺失,如 getxattrat()和 setxattrat()。有些函数比如 fchownat()和 faccessat() 并没有完全遵循这个模式。

Allison 用了很清晰地描述:"所以我们原来干净漂亮的 POSIX 文件系统 API 看起来不再干净漂亮了……路径名作为一个概念,现在在 POSIX 中被完全破坏了。" 这里表达出的痛苦,其实部分来源就是 Allison 在修复 Samba 的 CVE-2021-20316 的漫长道路上的挣扎。

由于演讲的重点是符号链接导致了 Unix 路径名 API 复杂化的问题,Allison 没有专门介绍那些哪怕没有符号链接也会产生的路径名相关的竞态冲突。这里的麻烦的来源之一就是缺乏一种机制,可以将对目录遍历以及对符号链接处理、最终在文件上执行一些操作,都统一成一个原子操作。

Workarounds

Allison 随后解释了在 open() 中使用 O_PATH 标志的作用,这会得到一个文件描述符作为返回值,该描述符只可以用来作为 dirfd 参数传递给 *at() 系列的系统调用。不幸的是,对 Samba 来说,用 O_PATH 打开的文件描述符不能用于读写扩展属性(extended attributes)。他找到了一个解决方法,通过一段代码演示,他把这段代码描述为 "我见过的最漂亮的 hack 方法之一,它丑得让你想吐,但它很神奇"。

int fd = openat(dirfd, "file", O_PATH|O_NOFOLLOW);sprintf(buf, "/proc/self/fd/%d", fd);getxattr(buf, ea_name, value, size);

proc/self/fd 中的内容是符号链接,代表进程所打开的每个文件描述符。Allison 解释了这段代码。"如果你把 '/proc/self/fd' 打印到一个 buffer 中,然后再把你刚从 O_PATH 得到的文件描述符的编号打印出来,你就可以把它作为一个路径传给 getxattr()或 setxattr(),而且这种方法不可能被中途插入符号链接来破坏。" 他不确定应该把这段代码归功于安卓还是红帽的开发者,但在 open()手册页中可以找到/proc/self/fd/的类似用法。

Allison 重申了他演讲的主要内容。"pathname 路径名的概念在 POSIX 上是完全无法用的。对于一个不是无人问津的应用软件来说,普通开发者在基于 POSIX 代码,那么你的代码中一定会有符号链接竞争问题。"

然后他提供了一些 CVE(已修复)的例子,包括 Rust 标准库中的一个例子,曾经得到过广泛讨论。在演讲的最后几分钟,Allison 注意到了 LWN 读者提出的几个解决方案,包括一个特殊的 prctl() 调用以及限制非 root 的符号链接。他说,MOUNT_NOSYMFOLLOW 这个 mount 选项是他首选的解决方案,可以用它来禁止在文件系统中跟踪符号链接。"这很完美。完全满足了我们的需要。" Allison 的演讲在此时结束了。

虽然以清理 POSIX API 的名义来禁止符号链接,似乎是可取的做法,但它们是一种经常使用的系统管理工具。有几个流行的 "符号链接管理器"。例如,Gnu Stow 就帮助管理员可以轻松地将程序安装到一个新的目录位置中,比如/usr/local/stow/package-version/,然后从/usr/local/bin/example 创建很多指向 /usr/local/stow/package-version/bin/example 的符号链接,确保仅仅会在必要的情况下来使用符号链接,从而减少数量。这使得在 stow -D 的帮助下,就可以通过删除符号链接来 "卸载" 一个软件包。

由 Debian 创建的/etc/alternatives 系统也允许管理员以类似的方式在可替换的软件包之间进行切换,而无需强制卸载或重新安装某个软件包。Nix 和 Guix 发行版也在类似情况下大量使用了符号链接,其中 Guix 配置文件是由一组树状的符号链接组成的,这些符号链接指向了安装在/gnu/store/的软件包,使得在特定版本的软件包的分组组合之间切换就变得很容易。

完全禁止符号链接会破坏这些使用场景,但限制它们的创建只能由 root 用户来进行,很可能就足够安全了。不过,用户可能仍然对符号链接有其他合理的需求,而大幅限制它们会是一个不受欢迎的改动。

SambaXP 提供了该讲座的视频和幻灯片。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

format,png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值