调用mprotect将栈上对象设为只读时进程崩溃,求助原因
问题
我希望将栈上对象的访问属性设置为只读,但调用mprotect后进程立即崩溃。大致代码如下:
constexpr int kPageSize = 4096; CHECK_EQ(kPageSize, sysconf(_SC_PAGE_SIZE)); volatile char padding1[kPageSize] = {0}; NetDataHead head; volatile char padding2[kPageSize] = {0}; // ... auto align_down_4k_ptr = steel::AlignDownPointer(&head, kPageSize); CHECK_EQ(reinterpret_cast<std::uint64_t>(align_down_4k_ptr) % kPageSize, 0); if (auto rc = mprotect(align_down_4k_ptr, kPageSize, PROT_READ); rc != 0) { // A LOG_F("Failed to mprotect ptr: %p, rc: %d", align_down_4k_ptr, rc); } // Read-only codes ... if (auto rc = mprotect(align_down_4k_ptr, kPageSize, PROT_READ | PROT_WRITE); rc != 0) { LOG_F("Failed to mprotect ptr: %p, rc: %d", align_down_4k_ptr, rc); }
程序直接在第一个mprotect调用的A点崩溃。查阅man手册后发现,Linux下这不应导致崩溃,请问可能的原因是什么?我想要用mprotect保护栈上的head结构体。
分析与解决
栈页默认包含执行权限,修改后破坏函数返回流程
多数Linux系统中,栈页默认权限是PROT_READ | PROT_WRITE | PROT_EXEC。当你将目标页改为仅PROT_READ时,会移除执行权限。而当前函数的返回地址就存在这个栈页里,mprotect执行完成后需要从栈中读取返回地址并跳转,此时栈页无执行权限,直接触发SIGSEGV崩溃。目标页包含栈帧关键控制数据
head所在的栈页同时存放了当前函数的栈帧信息,比如基址指针(rbp/ebp)、返回地址。将页设为只读后,后续函数执行时(比如mprotect返回时)需要修改这些栈帧数据,会触发写权限错误,导致崩溃。padding无法保证head独占一页
栈是从高地址向低地址增长的,padding1的地址比head高,padding2比head低。AlignDownPointer(&head, kPageSize)得到的页会包含padding1的部分数据和head,而这个页属于当前函数的栈帧核心区域,修改权限必然干扰栈的正常运作。
可行方案
- 改用堆内存分配:用
mmap申请一个页大小的内存,初始化NetDataHead后设置为只读,操作完成后恢复权限再释放。这种方式能精准控制内存页权限,不会影响栈的正常运行。 - 避免直接修改栈页权限:如果必须用栈,需要将
head放在完全独立于当前栈帧控制数据的页中,但这种方式严重依赖栈布局,稳定性极差,不推荐使用。
内容的提问来源于stack exchange,提问作者magicsun
相关产品推荐
相关产品推荐

