LWN:从kernel向user space发送通知的一个通用方案

640点击上方蓝色字关注我们~



Generalized events notification and security policies

By Jonathan Corbet
June 11, 2019


从kernel开发的极早期开始,mailing list上就一直有讨论如何从kernel向user space进行event通知。LWN在15年前就有过文章进行介绍。现在也已经有了很多种专用的event-reporting API,不过没有一个是通用方案,能在一个统一的入口来获取所有event。David Howells最近提出了一种新的通知接口,希望利用ring buffer来将events传递给user space,避免使用过多system call。这个API本身没有太多争议,不过相关的安全模式引起了不少讨论。


/dev/watch_queue

Howells实现了一个特殊的设备,/dev/watch_queue。application可以先用write模式打开这个设备,然后利用IOC_WATCH_QUEUE_SET_SIZE ioctl()来设置ring buffer的size。目前的patch set实现中,只支持2的幂次的size,并且不能超过16(单位都是page)。完成之后,就可以用mmap()来将ring buffer映射到application地址空间。

Ring buffer会被分成多个8字节大小的slot,每个event可以占用多个slot,其中第一个slot都必然包含如下结构:


 
 

type和subtype是告诉application这里发生的event类型,如果是USB设备插入的话,会报出WATCH_TYPE_USB_NOTIFY和NOTIFY_USB_DEVICE_ADD的event。有一个特殊的WATCH_TYPE_META type和WATCH_META_SKIP_NOTIFICATION subtype,是用来指示这个entry不需要application处理,可以直接忽略。而info包含了ring buffer的溢出或者event因为内存不足导致丢失等异常情况的flag,同时也记录了这个event占用了几个slot。


ring buffer本身看起来是这个样子:


 
 

这里使用了union,意味着meta结构成员里的head和tail指针会占用最初的3个slots。watch结构可以忽略,所以user-space代码只需要把header信息传进来即可。

如果head和tail相等,意味着buffer是空的。kernel要汇报下一个event的时候,就会插入到head指向的slot的未知,同时将head后移。在ring buffer末尾,如果剩余slot数量不够放下一个event了,就把末尾空闲slot用上面讲过的特殊类型event填入,从而被跳过,然后在buffer开头来放置真正需要传递的下一个event。user space会从tail的位置开始读取并处理event,并后移tail的位置。提醒一下,在修改ring的指针的时候,务必要调用相应的memory barrier操作。Application可以调用poll()来等待下一个event。


Selecting events

还需要告诉kernel,自己这个application关心哪些event。每个子系统都有自己的方式。这个patch set里面实现了若干种event来源:



  • 跟key有关的event,可以通过调用keyctl()系统调用并传入KEYCTL_WATCH_KEY来申请。


  • 文件系统的mount/umount event,可以通过调用一个新加的watch_mount()系统调用来申请。


  • 对某些特殊文件系统的特有的event(称为superblock events),可以通过新加的watch_sb()系统调用来申请。


  • 还增加了一个系统调用watch_devices(),允许申请某些硬件相关的event。已经包含了USB和块设备的event支持。



缺省来说,所有申请过的event都会被发送到ring buffer里,如果aplication只关心其中的一个子集,可以利用下面这个结构提供的过滤机制:


 
 

application利用type来设置自己关注的event类型,subtype_filter是一个bitmask,用于过滤筛选subtype。info_filter和info_mask可以用来做更复杂的过滤筛选。符合下面条件的event都会被收到:


 
 

也就是说,info_mask指示info_field里面的哪一部分是我们关注的,而info_filter就是设置我们关心的值。

Application可以设置若干个过滤条件,放进如下结构:


最终用IOC_WATCH_QUEUE_SET_FILTER ioctl()传递给kernel。

更多详细信息还请直接查看这个patch set最开始的描述:https://lwn.net/ml/linux-kernel/155991709157.15579.17714751502536525102.stgit@warthog.procyon.org.uk/


Security

大家都认为kernel信息是敏感信息,不应该传递给任何user space进程。在5月28日的上一个版本patch set中,跟key(秘钥)有关的event,只会传递给那些对相关秘钥有“查看”权限的application;mount相关event可以随意查看;superblock event也没有限制;当时patch set还不支持普通硬件设备的event;块设备子系统的event也没有访问限制。大家认为当时的管控过于松懈了,有不少问题。其中一个观点是多数讨论者此前都没有考虑过的。以mount event为例,大家都在争论process B什么情况下应该有权限收到process A进行文件系统mount/unmount的时候产生的event,不过一般大家都只在争论B是否是特权进程,或者是否有权限监控A在做什么事情。而Casey Schaufler提出了一个反向思考的观点:B要看到A产生的event的话,首先要A有权限发送signal给B:

如果process A发送signal给process B,kernel会检查A是否跟B有相同的UID,或者A是否有特权。这时B是被动的,而A是主动发起方。在本文讨论的event delivery这种场景,A做了一些动作(例如修改keyring)产生了某个event,并被通知到process B的event buffer。这里同样A是主动方,B是被动方。此时A应该要对process B的event buffer有写权限才合理。他认为,如果不这样做,就会给进程之间开放了一些秘密通讯通道,很难管理。不过其他讨论者觉得不用担心这里所谓的秘密通讯通道。因此当时这个patch set就反复讨论这些权限管控策略,一直没有定论。期间Howells确实实现了Schaufler要求的管控策略配置接口。最后Andy Lutomirski认为这是一个根本性的错误设计。有一个反直觉的例子可能会有助于帮助理解。SELinux的维护者Stephen Smalley指出,如果两个进程能够map同一个文件,他们就能通过这个文件来进行通讯,因此限制对这个文件的activity的通知消息,并不会增强安全性。Schaufler举了一个例子(/dev/null)来反对这个说法,Lutomirski最终赞同他的说法,针对/dev/null这类文件来说,不应该让不相干的进程能互相trigger通知消息。由此,开启了新的一轮关于notification的security policy的讨论。


目前,comunity还没有完全达成一致该如何处理这类policy,有可能需要花一段时间。这是一个复杂的新API,解决安全问题之前估计这个patch set无法合入mainline。不过至少这次这个patch set看起来比起其他的通用通知机制要更加有机会合入upstream。



全文完

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

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


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


640?wx_fmt=jpeg

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值