在调用makecontext
之前,为什么我们需要设置堆栈大小ss_size
?
我刚刚有一个makecontext/swapcontext
片段的单元测试用例,它失败了SIGSEGV
.发生的事情是堆栈大小太小,不相关的内存(碰巧是一些独特的指针(被损坏并报告了段错误。所以段错误在这些不相关的指针上,我本可以有例如一些字符串,然后内存损坏不会被注意到。
我本以为当堆栈大小ss_size
不够时,SIGSEGV
会立即提高,但是 考虑到上述内存损坏,我得出结论,不可能从这里的SIGSEGV
中恢复。这让我回到了这个问题,为什么我们需要首先设置堆栈大小,当它不用于发出溢出信号时?它的用途是什么?
编辑:
好吧,这都是关于makecontext的(3(。这些函数仍然用于绿色线程、协程等。考虑到这些任务(在我看来(也不是在 c++ 中,它们没有真正的替代品。
在 getContext(3( 中定义的ucontext_t
中uc_stack
需要 sigaltstack(2( 中定义的ss_size
。
下面是一个显示内存损坏的最小可验证示例,通过"绘制"内存,如上所述。
#include <iostream>
#include <ucontext.h>
#include <memory>
#include <cstring>
#include <stdio.h>
#include <unistd.h>
ucontext_t caller, callee;
void cb(void){
//paint stack with 2
char tmp[7000];
std::memset(tmp,2,7000);
//note stack size specified 6k bytes in size
//this should not be allowed.
//furthermore there is not even some signal raised here
//i expected raised SIGSEGV when this call stack exceeds ss_size
//it makes ss_size useless no?
}
int main(){
//
std::memset(&caller,0,sizeof(caller));
std::memset(&callee,0,sizeof(callee));
//create stack and paint 0
std::unique_ptr<std::byte[]> stack(new std::byte[10000]());
std::memset(stack.get(),0,10000);//paint stack 0
//make context
//note stack specified to [2000,8000)
//that means [0,2000) and [8000,10000) should not be touched
if(getcontext(&callee) == -1) {std::cout << errno << ":" << std::strerror(errno) << std::endl; return 1;}
callee.uc_link = &caller;
callee.uc_stack.ss_sp = stack.get()+2000;
callee.uc_stack.ss_size = 6000; //what is this line good for, what is it guarding?
makecontext(&callee,cb,0);
//swap to callee
if(swapcontext(&caller,&callee) == -1) {std::cout << errno << ":" << std::strerror(errno) << std::endl; return 1;}
//print color - should be 0
//if 2 then memory corrupted by callee
std::cout << int(stack[996]) << std::endl;
std::cout << int(stack[997]) << std::endl;
std::cout << int(stack[998]) << std::endl;
std::cout << int(stack[999]) << std::endl;
return 0;
}
我再次不明白的是为什么我们需要 设置堆栈大小ss_size
,因为它看起来没有用于防止内存损坏或其他任何东西。看起来它只是在那里,但没有任何用处。但我不敢相信它没有用。那么它的"守卫"/好处是什么?
好吧,我不想给这带来更多的混乱。目标是通过安装信号处理程序来恢复SIGSEGV
摆脱固定大小的函数调用堆栈,但由于内存损坏,这看起来是不可能的任务;或者有一个可扩展的堆栈,例如使用带有MAP_GROWSDOWN
标志的 mmap(2(,但这看起来很糟糕,因此不是一个选择。
callee.uc_stack.ss_size = 6000;//这条线有什么用,它保护什么?
此行集是堆栈大小(正如您可以在 man sigalstack 中读到的那样(。从 glibc 读取 makecontextss_size
用于确定堆栈的结束,glibc 在其中设置新上下文的堆栈。因为某些机器上的堆栈"向数字较低的地址增长"(就像在 x86 架构和 wiki x86 上一样(,makecontext
需要/希望将其数据放在堆栈的末尾。所以它需要确定堆栈的结束,这就是ss_size
的用途。
将ss_size
设置为任何值并不意味着堆栈大小溢出将向进程发出操作系统信号,通知进程尝试访问受限内存区域。*context
的实现不是(也不应该(旨在使地址ss_sp + ss_size (+ 1)
为内核保护的内存,因此写入该地址将触发分段错误。这仍然是所有正常变量。与写入未知内存位置(例如溢出数组(一样,无效地址可能恰好位于进程地址空间内,因此根据内核,进程将在其地址空间内写入,一切都很好。正如你在这里所做的 - 你的cb
函数写入内存new std::byte[10000]
从内核的角度来看,这没有任何问题。
您很可能可以在 valgrind 或 gdb 或其他工具下分配new std::byte[6000]
并运行您的进程来检查恶意写入。