内存保护键(MPK):禁用pkey0写权限时异常处理器崩溃
解决MPK禁用pkey0写权限后信号处理崩溃的问题
问题背景
在x86/Linux环境中,利用内存保护键(MPK)和保护键寄存器PKRU实现基于内存保护域的进程内隔离。程序执行流程如下:
- 先运行管理员代码,分配新保护键并关联对应内存,将用户栈指针切换到该内存后进入用户代码执行
- 用户代码触发异常时,信号处理器可将执行权交回管理员,此功能原本正常工作
为限制用户代码的破坏范围,需要禁用非用户所属内存的写权限,尤其是设置PKRU.WD0=true(即PKRU寄存器值设为0x55555552)来禁用pkey0关联页面的写权限。但此时出现异常:
- 用户代码触发FPE异常时,进入
handler_asm()就直接崩溃 - 触发SEGV异常时,会在
unblock_signal()调用链中崩溃
需要解决:如何避免该崩溃?是否存在操作错误?或是禁用pkey0写权限时无法正常处理异常,违背MPK的设计初衷?
问题根源与解决方案
1. 核心操作错误:pkey0的特殊属性
Linux下MPK的pkey0是默认保护键,所有未显式分配其他保护键的页面都会默认关联pkey0。内核的信号处理代码、进程的默认栈、libc的全局数据等核心区域都属于pkey0关联页面。当你设置PKRU.WD0=true禁用其写权限时,信号处理过程中内核/用户态的信号处理代码需要修改pkey0关联的内存(比如修改栈帧、更新信号掩码等),此时会触发写权限违规,直接导致崩溃——这并非MPK设计缺陷,而是你误用了pkey0的默认属性。
2. 正确的权限配置方案
- 不要修改pkey0的权限:保留pkey0的默认读写权限(PKRU.WD0=false),将用户代码的隔离内存分配到其他非0的保护键(比如pkey1、pkey2等),然后只针对这些非0保护键设置严格的权限(比如禁用管理员代码对其的写权限,或禁用用户代码对其他保护键的访问权限)。
- 信号处理栈的特殊处理:确保信号处理使用的栈(无论是默认栈还是通过
sigaltstack()设置的替代栈)关联的保护键拥有完整的读写权限,避免信号处理过程中触发权限异常。
3. 异常处理流程的修正
当用户代码触发异常时,信号处理器在接管执行前,需要先临时恢复pkey0的写权限(修改PKRU寄存器),待信号处理完成、准备交回管理员代码时,再重新设置严格的权限规则。这样既能保证信号处理的正常执行,又不影响用户代码的隔离限制。
4. 关于MPK设计初衷的说明
MPK的设计初衷是实现进程内的内存域隔离,pkey0作为默认键是为了兼容原有未使用MPK的代码和内核逻辑。禁用pkey0的写权限会破坏进程的核心运行环境,这属于错误的配置方式,而非MPK本身的缺陷。
内容的提问来源于stack exchange,提问作者Robert Kueffner
相关产品推荐
相关产品推荐

