宋宝华:让Linux的段错误(segmentation fault)不再是一个错误

今天周末,娃儿们配合不闹事,写一篇短小精悍的文章吧,反正文章长了大家也没时间看。今天文章的目标是,如何在进程访问空指针等情况下,产生段错误后,不再退出而是继续运行。

这件事情,对于熔断(meltdown)漏洞的利用是比较关键的,我们都知道,利用meltdown漏洞,在用户空间是可以偷窥到内核空间数据的,下面的应用程序访问内核地址k:

meltdown漏洞攻击concept代码

array[256];

int c = *k; //k是内核地址

array[c]; //这句话产生了array[c] cache命中的副作用

之后,我们判断array[0..255]里面谁访问的最快,利用这种时间旁路攻击,获知k地址存放的是c。

在meltdown漏洞的分析里,我们重点关注了时间旁路攻击的好玩的地方,尤其以“李小璐汉堡问题”,说清楚了这种攻击的原理。但是我们忽略了一个事实,int c = *k; 这句话,本身,是一定会引起段错误的,因为用户空间做了越权访问。

其实,在熔断漏洞的代码中,对SIGSEGV信号,进行了特殊的处理。它截获了SIGSEGV的信号:

它实际捕获了SIGSEGV信号,在信号发生后,修改了CPU的跳转地址。

众所周知,在Linux里面,进程对信号的处理有三种方法,捕获、忽略、缺省行为。一般情况下,你什么都不做,行为就是缺省的,比如CTRL+C就会让进程死。

但是下面的进程,ctrl+c之后不仅死不了,还能打印东西:

原因很简单,它的2号信号SIGINT被我们捕获了。

这个从ps命令里面也可以看出(留意IGNORED和CAUGHT这2列):

当然,我们也可以完全不鸟它,选择忽略:

这个时候,按ctrl+c就什么鬼都没有:

我们还是可以ps一下(留意IGNORED和CAUGHT这2列):

在这么多信号里面,除了极少数信号类似SIGKILL、SIGSTOP这种不能捕获和忽略以外,其他的都是可以被我们拿来把玩的,当然也包括看起来牛逼轰轰的SEGV段错误(编号11)的信号。

我必须反复强调一点,当你用ctrl+c等对应的信号去杀死一个进程A的时候,从来都不是你杀死了A,而是你给A发了个信号,而A在响应这个信号的时候,其对应行为是进程exit。所以,不是你杀死了进程A,而是你发个信号,“通知”目标进程A去死。所以你不能用人类世界的杀死来理解Linux的杀死。Linux的逻辑类似: 你对进程A说:“你去死吧”(发个可以让它死的信号),A看到这个pending的信号后,啥废话都不说,立即闷声死翘翘!所以,你只要改变A的响应行为,就可以选择不死。

下面的这段代码,利用了setjmp和longjmp来实现段错误后继续执行:

setjmp这个函数的原理是:

调用这个函数的时候,它会保存执行现场,并返回0;之后调用longjmp,可恢复到setjmp保存的现场,setjmp再次返回,不过这次该函数返回非0。我们在代码里面标注下1-5的时间轴可能能方便大家理解:

第1和4步都在setjmp这个位置。上述例子中的关键点是,对SIGSEGV进行了捕获,并在SEGV的信号处理函数中,进行了代码的跳转。所以它的执行结果如下:

如果我们删除了绑定SIGSEGV的这行代码:

则执行结果为:

在执行*p = 0的时候,得到了我们期待的段错误。

(您的打赏是小弟持续原创的动力 ^-^)

Linux阅码场原创精华文章汇总

更多精彩,尽在"Linux阅码场",扫描下方二维码关注

点一点右下角”在看”,为阅码场打Call~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宋宝华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值