1

在调用之前makecontext为什么我们需要设置堆栈大小ss_size

我刚刚有一个片段的单元测试用例makecontext/swapcontext,它失败了SIGSEGV。发生的事情是堆栈大小太小并且不相关的内存(恰好是一些唯一的指针)被损坏并报告了段错误。所以段错误在这些不相关的指针上,我可能有一些字符串,然后内存损坏就不会被注意到。

我本以为当堆栈大小不够时会SIGSEGV立即提高,但考虑到上述内存损坏,我得出结论不可能从这里恢复。这让我回到了这个问题,为什么我们需要首先设置堆栈大小,而不是用来发出溢出信号?这有什么用途?ss_sizeSIGSEGV


编辑:

好吧,这都是关于makecontext(3)的。这些函数仍在用于绿色线程、协程等。考虑到这些任务(在我看来)也不是在 c++ 中,它们没有真正的替代品。

ss_size在getcontext(3)中定义需要在sigaltstack(2)中定义。uc_stackucontext_t

按照上面描述的通过“绘制”内存来显示内存损坏的最小可验证示例。

#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,但由于内存损坏,这看起来像是不可能完成的任务;或者有一个可增长的堆栈,例如使用带有标志的mmap(2)MAP_GROWSDOWN,但这看起来很糟糕,因此不是一个选项。

4

1 回答 1

1

被调用者.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]内存,从内核的角度来看,这并没有错。

您很可能可以new std::byte[6000]在 valgrind 或 gdb 或其他工具下分配和运行您的进程来检查恶意写入。

于 2019-12-04T11:15:36.987 回答