LWN:利用openat2()来限制文件路径查询

640

点击上方蓝色“Linux News搬运工”关注我们~

Restricting path name lookup with openat2()

By Jonathan Corbet
August 22, 2019


根据文件路径查询出具体的文件对象,看起来是一个很简单的工作,不过其实这是kernel所实现的最复杂的功能之一。如果需要确保对恶意用户提供的path也要能正确处理而不至于损害系统,那就更加复杂了。在2014年就开始有一些工作希望能让open()和openat()系统调用更加安全,当时是试图加一个O_BENEATH,不过后面仍有无数问题需要处理。Aleksa Sarai已经在这个领域研究过一段时间了,现在他给出的结论是需要创建一个新的openat() API(命名为openat2())才能真正解决问题。


增加openat2()的直接原因是希望能让进程安全的打开一个路径,哪怕此路径是由攻击者控制的也无法破坏系统。实际操作中,这就意味着需要在根据路径查找文件的这部分工作中增加一些限制。过去人们试图给openat()增加flag来解决这个问题,不过没法解决干净:openat()不会检查不认识的flag,而且能用来增加新的含义的flag bit也不多了。经常攻击者会利用无法识别的flag来进行攻击。如果某个进程希望使用限制访问的flag,那它首先就需要确认一下kernel是否支持这个flag;要么就需要接受kernel在碰到不支持的flag可能会出现安全漏洞这个事实。


不过,如果更改openat(),从而在收到不认识的flag的时候返回错误值,这种做法并不合适,因为这会让现有的用到openat()的程序行为被强制改变了。所以唯一的方案就是来建立一个新的系统调用确保会检查flag,这就是openat2():

    struct open_how {
__u32 flags;
union {
   __u16 mode;
   __u16 upgrade_mask;
};
__u16 resolve;
__u64 reserved[7]; /* must be zeroed */
   };

   int openat2(int dfd, const char *filename, const struct open_how *how);

(注意,这个接口形式是kernel对user-space暴露的接口,通常C函数库会封装为另一套API更加方便程序调用。)


Sarai没有简单的增加一个参数用作flag设置,而是把所有会影响行为的信息都放到一个独立结构里面去了。这样openat2()就跟open()和openat()的行为不一样了,因为openat2()永远都是使用固定个数的参数。flags域包含了open()和openat()已经支持的o_flags内容,不过如果在flags域使用了未知的bit则会报错。mode成员的含义仍是指定创建新文件的时候的权限信息。


resolve成员则是包含了一组新的flag标志,可以控制路径查找时候的行为。在这次的patch set里面已经实现了的flag包括:

  • RESOLVE_NO_XDEV

    • 路径查找的时候确保不会越过mount点(也包括bind mount)。也就是说,打开的文件必须是跟dfd描述符在同一个mount的文件系统里。如果dfd是AT_FDCWD的话则跟当前工作目录在同一个mount的文件系统里。

  • RESOLVE_NO_MAGICLINKS

    • 在一个文件系统目录里面只有少数几类对象,包括普通文件,目录,设备,FIFO,符号链接。Sarai希望使用这个选项来处理另一类多年来未得到正式承认的类型“magic link”。具体例子比如说/proc/PID/fd目录下面的那些链接,都是由kernel实现,具有一些很特别的属性。使用这个flag能够防止路径查找的时候去遍历进入那些magic link,这样就可以避免某些类型的攻击。例如container内部的程序利用/proc/目录下面的指向已经打开文件的描述符的链接来访问到container以外的数据。

  • RESOLVE_NO_SYMLINKS

    • 禁止跟随符号链接(包含magic links)继续查找。这个选项跟O_NOFOLLOW flag还是有区别的,因为它会禁止在查找过程中任何位置来跟随链接跳转,而O_NOFOLLOW只会对路径里面最后一级(意指最终文件或者目录)有效。

  • RESOLVE_BENEATH

    • 查找过程必须局限在起始点一下的目录树中,如果利用类似“../”来试图跳出这支目录树的话,就会返回错误。

  • RESOLVE_IN_ROOT

    • 使用这个flag的话,表现就好像是首先chroot()到起始位置了一样。这样所有绝对路径的效果都是相对于起始位置的,而“../”也无法走到这个目录之外。之前做的为了让RESOLVE_IN_ROOT避免race condition的一些改动导致chroot()受到影响,细节见这里:https://lwn.net/ml/linux-kernel/20190820033406.29796-7-cyphar@cyphar.com/

在本次的patch set里面magic link相关的问题提到好几次。RESOLVE_NO_MAGICLINKS参数会确保查找过程中不会跟随magic link深入进去,看起来还有不少场景其实是需要能跟随magic link的。这里的问题是如果允许跟随magic link深入的话,可能会有风险。例如二月份报出的runc container breakout vulnerability就是因为恶意代码利用/proc/PID/exe链接来打开了runc程序,并拥有了写入权限。简单来说,这会导致container容器的限制失效。


第一个patch更改了open()在碰到magic link时候行为,包括所有open()的变种函数都有影响。此前的做法是忽略magic link本身的权限bit配置,现在则会受这些权限bit设置的控制。距离来说,/proc/PID/exe的权限可以在kernel里配置好,确保没法用write权限来打开此文件,这样就能组织runc breakout攻击。


此外还在openat2()里面提供了另一个功能(包括openat()也一样有这个改进了),那就是增加了一个O_EMPTYPATH flag,用来要求kernel忽略path这个参数。这样调用这个函数的时候就只会重新打开dfd参数所传入的文件描述符,只不过是用新的mode参数来打开。例如很常见的一个应用场景,就是此前已经用O_PATH flag打开过的一个文件仅仅能做一些file descriptor level的操作,无法访问文件内容,那么重新打开一次的话就能够访问文件内容或者metadata了。详情可以看man 2 open里面的O_PATH相关介绍。此前程序要这么做的话,会通过对/proc/PID/fd路径来重新打开,不过使用O_EMPTYPATH之后哪怕无法访问/proc目录也能实现这个目的了。


最后一点,在这种重新打开用O_PATH flag打开过的文件进行这种“升级”操作的时候,这个新的API还允许施加一些限制。在用openat2()通过O_PATH参数打开文件获取文件描述符的时候,可以利用how结构里的upgrade_mask成员来配置今后再次打开这个文件描述符时有哪些访问限制。如果使用了UPGRADE_NOREAD,则会禁止重新打开的时候授予读权限。而UPGRADE_NOWRITE择可以避免获得写权限。这样就能限制恶意程序在对O_PATH文件描述符进行重新打开时的危害范围了。


这个patch set的前几个版本已经经过了很多讨论。这次最新版本贴出来之后很安静,说明此前提出的大多数反对意见都已经得到解决了,或者也许review者都还在参加Linux Security Summit所以没仔细看邮件。无论如何,我们都很需要有办法来对文件路径查找的过程进行限制,所以或迟或久,这个patch set总会合入的,只是看还需要做多大改动而已。


全文完

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

极度欢迎将文章分享到朋友圈 
热烈欢迎转载以及基于现有协议修改再创作~


长按下面二维码关注:Linux News搬运工,希望每周的深度文章以及开源社区的各种新近言论,能够让大家满意~


640?wx_fmt=jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值