mprotect/map原理及缺陷

《linux 关于虚拟内存的几个系统调用》,提及到用户程序可以通过mprotect或者map(MAP_FIXED)系统调用可以根据需要再次修改已经申请过的进程内存的访问权限,其修改原理主要就是申请page table entry中的读写权限,以一个经典的X86 64 entry为例:

每次通过mprotect/map修改权限时,都会修改地址转换表中相对应的的page table entry中的R/W。由于通常CPU地址转换是以page(4K)大小为单位,所以每次修改权限都是一次性修改整个page。那么如果每次需要修改1G空间大小的权限,就需要同时修改 262144个页面,这时就需要遍历每个页对应的PTE(page table entry)进行修改。如果一个应用程序中需要不断修改权限,那么就需要不断修改对应的PTE,其性能显然不能满足要求。

The memory-management units built into most contemporary processors are able to control access to memory on a per-page basis. Operating systems like Linux make that control available to applications in user space; the protection bits supplied to system calls like mmap() and mprotect() allow a process to say whether any given page should be readable, writable, or executable. This level of protection has served for a long time, so one might be tempted to conclude that it provides everything that applications need. But a new hardware feature under development at Intel suggests otherwise; the first round of patches to support this feature explore how programs might gain access to this feature.

为了解决上述性能问题,intel首次提出了“Memory protect keys”方案,并在64位cpu中首次实现。

Memory protection keys(pkeys/MPK)

memory protections keys是由intel 在Skylake 服务器cpu上实现其原理:为了提高修改权限性能,专门从pte中划分出4位共16个区域,指向一个专有寄存器用于控制权限,每个pte表都可以利用这专门的四位指定需要使用的哪个寄存器权限,这样修改权限是只需要修该该寄存器即可,从而避免修改每个页表的pte:

A pkey is a four-bit value (in the current Intel implementation) that can be stored in the page-table entry for each page in a process's address space. Pages can thus be arbitrarily assigned to one of sixteen key values; each address space has its own set of keys. For each of those keys, the process can configure the CPU to deny either write operations or all access entirely. Pkeys will override the regular protections assigned to each page but, since they can only deny operations, their effect will always be to restrict access more strictly than the page protections do. There are a number of intended use cases, including the implementation of execute-only memory or the protection of sensitive data (cryptographic keys, for example) when it is not in active use.

AMD 64位cpu后来也支持该硬件特性,以AMD 64位 cpu来说明其硬件原理:

amd 64位cpu 其pte默认位4K对齐,每个pte entry中,硬件如下:

每个entry中将59:62位共4个bit位用作 Memory Protection Key(AMD中简称MPK),索引 用于选择使用使用哪个保护位策略,选择的范围共0~15个共16个可以选择的内存保护策略即PKeys/MPK.

MPK实际上指向的是一个32位的PKRU寄存器,该寄存器格式如下:

每个WDI/ADi分为一组或者一对,PKRU寄存器共32位可以划分成16个Pkeys,而PTE存放的Pkeys正是决定使用PKRU 寄存器中哪个配置策略。

  • ADi用来控制对应的内存是否可以访问,如果ADi设置为0,则表明实际可以被访问,如果设置为1 则数据不可以被访问
  • 如果WDi设置0,则配置为拥有写内存权限,如果WDi设置为1,则用户模式下不拥有写权限。
  • 但是在超级用户 ,其写权限有CR0.WP来控制,CR0.WP=1,则超级用户没有该内存写权限,CR0.WP=0,超级用户拥有写权限。

PKRU硬件操作

使用MPK功能之前,首先需要通过CR4.PKE设置为1 开启 MPK功能,可以从CPUID Fn0000_0007_ECX_x0中读取,并且也可以有内核来启动:

OS可以通过设置CPUID 0x0000_0007寄存器OSPKE位,来开启PKE功能。

对与PKRU Register ,软件可以通过RDPKRU和WRPKRU两个指令集进行读和写 PKRU Register.

RDPKRU

RDPKRU 读取PKRU 寄存器,其指令格式如下:

RDPKRU 可以在任何权限模式下使用该指令,且只有在CR4.PKE设置之后才有效,指令操作码位“0F 01 EE", 主要是将PKRU 寄存器中的内容读取到EAX通用寄存器中,并将EDX 通用寄存器中的内容清零, 内核中将上述操作封装到rdpkru()函数中:

static inline u32 rdpkru(void)
{u32 ecx = 0;u32 edx, pkru;/** "rdpkru" instruction.  Places PKRU contents in to EAX,* clears EDX and requires that ecx=0.*/asm volatile(".byte 0x0f,0x01,0xee\n\t": "=a" (pkru), "=d" (edx): "c" (ecx));return pkru;
}

直接采用内嵌汇编形式,期中a 位EAX寄存器意思,d位EDX寄存器,C为ECX寄存器,将PKRU寄存器中的内存读取到变量pkru中。

WRPKRU

WRPKRU指令主要是将配置的权限策略写入到PRRU中,其指令格式如下:

上述指令操作码意思就是将EAX通用寄存器中的内容写入到PKRU中 ,因此设置PKRU需要通过EAX寄存器,内核封装函数为wrpkru

static inline void wrpkru(u32 pkru)
{u32 ecx = 0, edx = 0;/** "wrpkru" instruction.  Loads contents in EAX to PKRU,* requires that ecx = edx = 0.*/asm volatile(".byte 0x0f,0x01,0xef\n\t": : "a" (pkru), "c"(ecx), "d"(edx));
}

关于Pkeys系统调用

linux 内核中关于Pkeys系统调用主要是如下:

#include <sys/mman.h>int pkey_alloc(unsigned int flags, unsigned int access_rights);int pkey_mprotect(void *addr, size_t len, int prot, int pkey);int pkey_free(int pkey);
  • pkeys_alloc:主要是申请一个pkeys,返回值为申请到的PKRU寄存器索引,其中索引0为默认使用, access_ringhts申请到的pkeys使用到的权限,可以设置为PKEY_DISABLE_ACCESS或者PKEY_DISABLE_WRITE.
  • pkey_mprotect: 将所需要内存区域【addr, addr+len-1】设置为使用申请到的pkeys权限策略。该系统调用可以认为是mprotect系统调用的扩展,pkey_mprotect系统除了支持mprotect设置【addr, addr+len-1】区域的proct权限之外,还支持pkey设置,其中proct权限对应的是page table中的entry权限即普通的权限控制,而pkey未PKeys权限设置,设置未page table中的CR4.Keys设置即该页表对应的key权限设置,硬件先优先使用key中的权限,然后才使用page table中的prot权限
  • pkey_free: 申请到的pkeys不使用时,将其释放。释放时,如果有内存映射区域正在使用该key,该区域中的内存映射使用的key并不会被删除,而且该key会被重新分配使用

So surprising things could happen if an application frees a key that is still applied to pages within its address space and the key is later reallocated to another use.

参考资料

https://lwn.net/Articles/615809/https://lwn.net/Articles/615809/

System calls for memory protection keys [LWN.net]https://lwn.net/Articles/689395/

Memory protection keys [LWN.net]https://lwn.net/Articles/643797/

AMD64 Architecture
Programmer’s Manual

linux内核那些事之Memory protection keys(硬件原理)相关推荐

  1. linux内核那些事之用户空间管理

    内核主要数据结构 linux内核将用户空间抽象成struct vm_area_struct进行管理,每申请以个用户空间在内核中都会抽象成对应的vm_are_struct进行管理,同时为了区别不同进程的 ...

  2. linux内核那些事之buddy(慢速申请内存__alloc_pages_slowpath)(5)

    内核提供__alloc_pages_nodemask接口申请物理内存主要分为两个部分:快速申请物理内存get_page_from_freelist(linux内核那些事之buddy(快速分配get_p ...

  3. linux内核那些事之buddy(anti-fragment机制)(4)

    程序运行过程中,有些内存是短暂的驻留 用完一段时间之后就可以将内存释放以供后面再次使用,但是有些内存一旦申请之后,会长期使用而得不到释放.长久运行有可能造成碎片.以<professional l ...

  4. linux内核那些事之buddy

    buddy算法是内核中比较古老的一个模块,很好的解决了相邻物理内存碎片的问题即"内碎片问题",同时有兼顾内存申请和释放效率问题,内核从引入该算法之后一直都能够在各种设备上完好运行, ...

  5. linux内核那些事之pg_data_t、zone结构初始化

    free_area_init 继续接着<linux内核那些事之ZONE>,分析内核物理内存初始化过程,zone_sizes_init()在开始阶段主要负责对各个类型zone 大小进行计算, ...

  6. linux内核那些事之Sparse vmemmap

    <inux内核那些事之物理内存模型之SPARCE(3)>中指出在传统的sparse 内存模型中,每个mem_section都有一个属于自己的section_mem_map,如下图所示: 而 ...

  7. linux内核如何修改lowmem,技术内幕:Android对Linux内核的增强 Low Memory Killer

    6 09 2013 技术内幕:Android对Linux内核的增强 Low Memory Killer Low Memory Killer(低内存管理) 对于PC来说,内存是 至关重要.如果某个程序发 ...

  8. linux内核那些事之mmap_region流程梳理

    承接<linux内核那些事之mmap>,mmap_region()是申请一个用户进程虚拟空间 并根据匿名映射或者文件映射做出相应动作,是实现mmap关键函数,趁这几天有空闲时间 整理下mm ...

  9. linux内核那些事之buddy(anti-fragment机制-steal page)(5)

    继<linux内核那些事之buddy(anti-fragment机制)(4)>,在同一个zone内指定的migrate type中没有足够内存,会启动fallback机制,从fallbac ...

最新文章

  1. C++11中std::condition_variable的使用
  2. P2P网络“自由”穿越NAT的“秘密”
  3. NASA告诉你四翼飞行器的飞行原理
  4. python Demo 01 爬取大学名称
  5. android 状态栏、标题栏、屏幕高度
  6. 英语笔记:写作:Nothing succeeds without a strong will
  7. Ext Designer
  8. java多线程入门1
  9. 中国机械压力机市场趋势报告、技术动态创新及市场预测
  10. 大数据时代已来,开发者该如何出击?
  11. C++之std::bind()用法
  12. Java-集合第二篇Set集合
  13. Atitit uke签名规范 与防伪鉴别 attilax总结
  14. 图片处理--连环画特效
  15. 信号与系统 Matlab 实验 画连续离散系统零极点图 分析连续、离散系统的频率特性 幅频特性曲线 相频特性曲线
  16. python无穷大怎么表示_python如何表示无穷大
  17. Microsoft edge已过期
  18. 用MATLAB实现对运动物体识别与跟踪
  19. 基于java民航售票管理系统源码(java毕业设计)
  20. [IOS] Storyboard全解析-第二部分

热门文章

  1. JeecgBoot 2.4 微服务正式版发布,基于SpringBoot的低代码平台
  2. Mac做深度学习开发【从无到有】
  3. rm、shutdown、磁盘挂载、vi使用方法
  4. 数据库“裸奔”再引祸端:VOIPO数百万呼叫和短信日志流出
  5. ZSH and oh-my-zsh 强强联合
  6. Hibernate Query数据查询
  7. Office 365系列之十二:ActiveDirectory同步
  8. 安卓项目之微信公众好---初体验
  9. 英文.数字和中文混合的彩色验证码【JSP】
  10. 信息系统项目管理系列之二:项目生命期和组织