【问题标题】:Considering makecontext() what is uc_stack.ss_size good for?考虑 makecontext() uc_stack.ss_size 有什么用?
【发布时间】:2019-12-04 00:26:02
【问题描述】:

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

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

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


编辑:

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

getcontext(3) 中定义的ucontext_t 中的uc_stack 需要在sigaltstack(2) 中定义的ss_size

在显示内存损坏的minimal verifiable example 之后,通过“绘制”内存,如上所述。

#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 标志,但这看起来broken,因此不是一个选项。

【问题讨论】:

  • 什么是ss_size,如何设置?请出示代码。我看到它用于signal stack?为什么在使用 systemV api 时需要使用它?为什么还要用这么老的api呢?
  • @KamilCuk 我已经更新了这个问题。希望这可以解决问题。
  • //note stack size specified 6k bytes in size //this should not be allowed. - SIGSEGV 发生和 预期 SIGSEGV 发生是不同的事情。如果您在允许的内存之外写入,您不能期望 SIGSEGV 发生,您的进程空间内可能恰好有一个有效地址。

标签: c++ ucontext


【解决方案1】:

callee.uc_stack.ss_size = 6000; // 这条线有什么用,它在保护什么?

这一行集合是堆栈大小(您可以在man sigalstack 中读到)。从阅读makecontext from glibc 开始,ss_size 用于确定堆栈结束,其中 glibc setups the stack 是新上下文。因为某些机器上的堆栈“向数字较低的地址增长”(就像它在x86 architecturewiki x86 上所做的那样)makecontext 需要/想要将它的数据放在堆栈的末尾。所以它需要确定堆栈的结尾,这就是ss_size 的用途。

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

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

【讨论】:

  • SMH!在您回答后查看我的问题,我应该能够在数字较低的地址错误绘制之后自己回答堆栈大小的需求。 makecontext from glibc 提供了深刻的见解。关于论点的小草图和cmets是金子。
猜你喜欢
  • 2018-05-14
  • 1970-01-01
  • 2011-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-13
  • 2017-04-26
相关资源
最近更新 更多