LWN回顾:vDSO,32位时间,以及seccomp功能

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

vDSO, 32-bit time, and seccomp

By Jonathan Corbet
August 2, 2019

seccomp()机制实在是太难用了。而且目前看来,这个机制还很容易一不小心就无法正常工作了,开发社区前不久一次为了year-2038问题所做的timekeeping改动,最终导致5.3 kernel里的seccomp()功能回退。开发者目前还在努力修补这个问题,不过32位系统上的seccomp()用户很有可能不得不在某个时刻改变他们自己的配置部分了。

vDSO机制(virtual dynamic shared object,虚拟动态共享对象)是kernel提供的一个优化,用来减少某些频繁调用的系统调用的时间开销。vDSO是在kernel提供的内存空间里面的一小块区域,通常都会被map到每个user-space进程的地址空间里去。这块区域里面包含了系统调用的实现,在某些情况下可以在user-space上下文就执行这些系统调用的工作。这样就能让调用者避免真的走完整的系统调用流程,从而能避免进出kernel产生上下文切换的开销。跟timekeeping有关的系统调用例如gettimeofday()都由vDSO实现了,因为这类调用需要在user space快速完成,并且调用的也特别频繁。

哪怕vDSO实现的功能在不同平台上都是类似的,不过还是在每个体系架构上都有不同的实现方式。在Linux 5.2开发周期中,Vincenzo Frascino增加了一个通用的vDSO实现方案,去掉了绝大多数的体系架构相关的代码,集中在了一套通用实现里面。在5.3的合入窗口,x86架构也切换到了这个通用的方案,看起来一切都很正常——直到出现问题。

seccomp() sadness

7月中旬,Sean Christopherson(还有其他一些人也发现了)报告了一个问题,上述通用的vDSO改动导致32位x86系统上的seccomp()配置没法正常工作了。seccomp(),允许user space来提供一个BPF program(这里指的是classic BPF,不是现在Linux系统里面到处都在用的eBPF)去控制它可以使用哪些系统调用。目的是希望能减少代码的可能受到攻击的暴露范围。虽然正确使用seccomp()很难,但是还是有越来越多的地方在使用seccomp()了。

本来vDSO能在user space得到timekeeping系统调用的功能,这下就不能保证可用了。如果进程在使用一个没被实现的clock,或者timekeeping的硬件无法利用vDSO来访问,那么vDSO必须要能自动回退成原来的调用进入kernel上下文的方案。在Linux 5.3之前,x86体系特有的vDSO方案使用了clock_gettime()调用,在32位内核上,这就会调用到32位的clock_gettime()。而32位time format会在2038年1月发生溢出。近期有很多工作在解决这个2038问题,不过尚未完全清理好。在这个前提下,kernel开发过程中肯定不会愿意再增加一个32位time接口,所以改成通用vDSO实现的时候就自然使用了clock_gettime64()来实现所有架构上的timekeeping系统调用,这是一个很自然的选择,毕竟没人会赞同在通用的vDSO实现里面增加另一个有2038年问题的代码。

这里就出现了一个问题。很多程序都会经常进行系统调用来了解当前的时间,因此一般来说使用seccomp()来进行进程的访问限制的时候通常都会允许gettimeofday()的调用,否则进程八成会无法正常执行。虽然让某个进程无法执行是基于安全考虑,不过用户通常都是非理性的,在这种情况下都会非常不满意。

因此,一个合理的seccomp() policy应该是在vDSO无法正常提供时间的时候能够允许回退到系统调用来提供时间信息。不过实际测试后发现,此前在32位系统上的安全策略都会允许clock_gettime()调用,不过却没有想到也会有需要调用clock_gettime64()。最终结果,当某个进程在5.3 kernel上被seccomp()策略保护住的话,在访问某个不被允许的系统调用时,就会很快的被暴力kill掉。

内核开发者可以辩护认为这个修改是为了2038年问题所必须的。他们也不会同意clock_gettime64()此前从来不用啊、或者5.1kernel之前根本没有这个系统调用啊诸如此类的这些借口。不过,最终效果上来说,出现了功能回退。内核社区有个天条就是要避免功能回退。毕竟,此前在某些特定的seccomp() policy保护下的进程也需要在5.3内核发布之后仍能正常工作。

Fixing the problem

已经有多种方案被提出来希望能解决这个问题,最开始是一个不太可行的建议:把通用vDSO改动直接revert掉。后来又有方案说,也许seccomp()规则应该把被vDSO所引发的系统调用忽略掉,不过这个主意很快就被拒绝了,毕竟模拟一个vDSO返回地址不是很难,会导致安全漏洞。一个可能的方案是专门指定让seccomp()不对clock_gettime64()进行保护,不过如果系统管理员希望能阻塞对timekeeping信息的所有访问的话,就无法实现了。然后大家开始讨论一个“system-call aliases”的概念,是由Andy Lutomirski提出来的,就是能把具有相等功能(接受相同类型的参数并且做了相同的工作)的系统调用都放到一个列表里面去。如果列表中某个函数被seccomp() filter判断为需要reject掉,那么kernel就要对列表中具有相同功能的函数都使用一样的策略。

这个alias的方案比起其他方案来说得到更多认同,不过它本身还是有些问题的。例如seccomp() policy的编写者可能天生希望区分对待那些相等的系统调用。总之这个方案可能会在某些场景下产生作者预料不到的结果。长久来说,alias可能还是最后的解决方案,不过正如Lutomirski指出的,“现在来发明一个新的seccomp功能来解决问题有点太晚了”,对5.3 kernel来说,还是需要一个更简单的方案。

Thomas Gleixner的一组patch可能有些帮助,这组patch会在32位系统上让vDSO回退到使用32位的clock_gettime()系统调用。这个方案大家虽然都不喜欢,不过却是能最快解决这个功能回退问题的方案。

可能最终还是需要其他解决方案的。毕竟我们不可能永远支持32位时间。一个方案就是让seccomp() policy的作者来更改他们的代码,放开对clock_gettime64()的限制。不过,哪怕能够实现并且大规模部署,开发者还是没有多大动力来做这件事,毕竟他们现有的policy需要能正常工作。按理来说应该采用一个multi-year deprecation process(经过几年过渡期的API废弃过程),来强制所有的policy都被改正。不过最终解决方案还是需要能在seccomp()内部来实现,可能是采用alias list(别名列表)或者其他一些异常处理的方式。能让所有人都满意的长久方案,目前还是看不出来会是什么样子。

这个情况其实也指出了seccomp()带有的一个问题:很难写好一组健壮性好、实现精确细节控制的policy。通常所实现的policy都很容易随着时间推移而失效。哪怕内核社区能永远保持兼容性,今后某个library里面的改动如果使用了一个新的系统调用,就可能让seccomp() policy产生不同的效果。虽然OpenBSD的pledge()机制可能无法提供seccomp()相同的控制程度,但是它所用的更广泛的功能分类就能更加容易的避免这类问题。不过Linux已经有了seccomp(),就得在受益于它的强大功能的时候承受它的复杂性。今后开发者很可能会再次无意中导致seccomp()相关的功能回退。

全文完

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

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

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值