考虑 makecontext() uc_stack.ss_size 有什么用?

Considering makecontext() what is uc_stack.ss_size good for?

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

我刚刚有一个 makecontext/swapcontext 代码段的单元测试用例,但它因 SIGSEGV 而失败。发生的事情是堆栈大小太小,不相关的内存(碰巧是一些独特的指针)被损坏并报告了段错误。所以段错误是在这些不相关的指针上,我本可以有例如一些字符串,然后内存损坏将不会被注意到。

我原以为当筹码量 ss_size 不够时 SIGSEGV 会立即被提升,但是 考虑到上面描述的内存损坏,我断定不可能从这里的 SIGSEGV 恢复。这让我回到了这个问题,为什么我们需要首先设置堆栈大小,当它没有被用来发出溢出信号时?它有什么用?


编辑:

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

ss_size 定义在 sigaltstack(2) is being needed for uc_stack in ucontext_t defined in getcontext(3).

在显示内存损坏的 minimal verifiable example 之后,"painting" 内存,如上所述。

#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,因为它看起来没有被用来防止内存损坏或其他任何事情。看起来它只是在那里,但没有任何用处。但是我不敢相信它没有用。那么 "guarding" / 有什么用呢?

好吧,我不想给这个问题带来更多的混乱。目标是通过安装 SIGSEGV 信号处理程序来恢复,从而摆脱固定大小的函数调用堆栈,但由于内存损坏,这看起来像是不可能完成的任务;或者有一个可增长的堆栈,例如使用 mmap(2) with MAP_GROWSDOWN flag, but this looks broken,因此不是一个选项。

callee.uc_stack.ss_size = 6000; // what is this line good for, what is it guarding?

此行设置堆栈大小(如您在 man sigalstack). From reading makecontext from glibc the ss_size is used for determining end of stack, where glibc setups the stack of the new context. Because stack on some machine "grows toward numerically lower addresses" (like it does on x86 architecture and wiki x86 中所读)和 makecontext needs/wants 以将其数据放置在堆栈的末尾。所以它需要确定堆栈的末尾,这就是 ss_size 的用途。

ss_size 设置为任何值 并不 意味着溢出堆栈大小将向您的进程发出操作系统信号,通知您的进程试图访问受限内存区域。 *context 的实现不是(也不应该)设计为使地址 ss_sp + ss_size (+ 1) 成为内核保护内存,因此写入该地址将触发分段错误。这仍然是所有正常变量。与写入未知内存位置和例如溢出数组一样,无效地址 可能恰好是 在您的进程地址 space 内,因此根据内核进程将在其地址 space 中写入,一切都很好。正如您在此处所做的那样 - 您的 cb 函数写入 new std::byte[10000] 内存,从内核的角度来看,这没有任何问题。

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