LWN:更加高效地保护内存不被kernel篡改!

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

Memory protection keys for the kernel

By Jonathan Corbet
July 21, 2020
https://lwn.net/Articles/826554/

memory protection keys 这个功能是在2016年加入4.6 kernel的,用来让user space来把各种page分组作为多个"protection domains"来管理起来,这样就可以在通常的page protection保护基础之上,再施加一些各自独立的访问权限限制。kernel里面一直没有类似的功能。所以这个地址空间中的kernel区域内存的访问都是由page protection来控制的。不过,这个情况马上就要变化了,得益于Ira Weiny的一组protection keys supervisor (PKS) patch set(其中许多patch都是来自Fenghua Yu)。

Virtual-memory系统是在page table里面记录维护着protection bit的,这些bit指明了在指定的处理器模式(processor mode)下允许的访问类型(read,write, or execute)。这些保护都是由硬件来确保的,kernel本身除了修改掉这几个bit之外也没有其他方法能绕过这个保护。因此,通常的page protection功能看起来足够确保这个kernel不会访问到那些它本不应该访问到的内存地址了。这个保护机制确实用在不少地方,比如用来确保kernel不会对它自己的代码段进行写入从而破坏自己的逻辑。

不过page protection在一种情况下不是很好用:有些memory在绝大多数时间不希望被kernel访问,但是偶尔还是需要访问一下的。如果要在这时去修改page protection的bit的话,这个操作会比较耗时,因为这里牵涉到了TLB invalidation等会影响系统性能的动作。因此频繁使用这个机制的话会损害kernel的性能。通常来说禁止kernel访问内存是用来预防kernel bug导致破坏的,而这种bug其实并不常见,因此多数人并不喜欢为了这个目的而损失性能。

因此人们希望能找到一种方法来能偶尔地让kernel可以进行访问。这就是PKS这个功能的目标了。它是针对未来的Intel CPU所准备的功能。PKS会对kernel地址空间中的每个page都跟一个4-bit的protection key关联起来,这样每个page都是属于16个内存区域中的一个。每个内存区域都可以被独立地配置为禁止kernel的写操作,或者彻底禁止kernel的所有访问。修改这些限制策略会比直接修改这些page的protection设置要更加高效。

这组patch set增加了一些新的函数来管理protection key,下面是它的分配和释放操作函数:

int pks_key_alloc(const char * const pkey_user);void pks_key_free(int pkey);

pks_key_alloc()是用来分配protection key的,这个 pkey_user 字符串只会用在相关的debugfs文件中。返回值要么是分配出来的key,要么就是一个负数的错误值表示分配失败。分配出来的key可以通过pks_key_free()来释放。

要用到protection key的代码,必须要处理好万一pks_key_alloc()出错这种情况。绝大多数系统上,接下来几年应该都是不支持这个功能的,很可能分配不到key。哪怕硬件具备这个功能,系统中也只有15个可用的key可供整个kernel来分配使用(因为key 0被缺省用来给所有page的缺省配置)。如果大家都在同时争抢key的话,那么可能会有子系统分配key的时候就会失败。不过也不用太担心,最坏情况,分配不到key的那些子系统也会仍旧按照目前他们的处理逻辑正常工作,所以一切功能都正常,只是没法使用额外的protection功能了。

把某个page跟响应的key关联并管理起来,可以直接使用通常的protection设置之后,利用PAGE_KERNEL_PKEY()这个宏来在protection相关设置中写好相应的bit。等key设置进来之后,就不再需要修改page的protection设置了。在第一次分配得到某个key的时候,需要先设置成禁止任何对相关page的访问。这个动作可以通过下面的函数来达到:

void pks_update_protection(int pkey, unsigned long protection);

这里的 protection 可以是0(表示可以允许传统的page protection设置中所允许的所有访问),或者 PKEY_DISABLE_WRITE、 PKEY_DISABLE_ACCESS。这个操作会相当快速,主要原因是它只影响当前的线程。这样,kernel里面的一个线程就可以被改成允许访问某个特殊的区域,而不会影响那些其他线程在运行的kernel代码。

可以想到kernel里面有不少功能都可以有帮助。如果能把存放加密秘钥的内存保护起来禁止任何访问,就可以让攻击者更难拿到这些秘钥了。不过,这个patch set的最初目标是希望能保护设备的memory,不被莫名其妙地改写掉。kernel会把外设上的memory给映射到kernel的地址空间里面来。这样的话访问会很快,但是这也引发了许多潜在问题,毕竟kernel总是有可能意外写到错误的位置的。

Kernel开发者在2008年做过一个著名的实验(https://lwn.net/Articles/304105/ ),通过写入某个错误位置,就毁掉了许多e1000e网卡。所以这确实有证明了这种问题是可能发生的,用户在无法正常连到网络上的时候肯定也会注意到这个问题。不过, persistent memory 的出现,让这种问题的风险变得更大了。这些memory存放的都是重要的用户数据,一次野指针导致的写入导致的数据破坏很可能短时间内用户也根本注意不到。Persistent memory可能会占用若干TB大小的地址空间,比起网卡来说要大得多得多,这样一来野指针导致的写入错误破坏概率也就更加大了。Linux肯定不希望被人看做一个会时不时导致persistent memory中的数据损坏的操作系统,这样一来就必须要利用一些有效的机制来进行额外保护了。

PKS patch set就可以提供这种保护,它只要对所有的device memory都分配一个protection key就可以了。如果想利用这种保护的话,设备驱动里面可以直接在这块memory对应的dev_pagemap结构中设置一个新的flag,这块memory起初的设置就是禁止write(但是允许read),每当kernel想要写入这块memory的时候,需要先调整访问权限限制,也就是利用下面这两个helper function:

void dev_access_enable();void dev_access_disable()

不过,大多数时候都不需要调用这些两个函数。如果不调用kmap()函数(或者它的某个变种)就直接访问device memory的话,本身就已经是个bug了。这个patch set里面改进了这些kmap()等函数,确保在kernel通过kmap()来获得某个受保护区域的mapping之后就自动打开write权限。也就是说,如果驱动程序里把某块memory标记成要protect的memory,然后针对这块memory调用kmap(),接下来永久持有这个mapping不释放掉,那么系统中使用这个机制的所有device memory都不再拥有这个额外的保护功能了。所以,希望所有用户都调用kmap_atomic(),这个API会潜在确保这个mapping是短期持有的,同时执行过程也更高效。

看起来大家基本达成一致,这个功能对kernel来说应该支持,不过还有一些关于它实现细节的讨论。因此很可能不会包含在8月份的5.9的合并窗口之内了。PKS应该会在下一个开发周期中合入kernel,这样就能尽最大努力来阻止kernel误写入persistent-memory device造成破坏了。

全文完

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值