今天周末,娃兒們配合不鬧事,寫一篇短小精悍的文章吧,反正文章長了大家也沒時間看。今天文章的目標是,如何在進程訪問空指針等情況下,產生段錯誤後,不再退出而是繼續運行。
這件事情,對於熔斷(meltdown)漏洞的利用是比較關鍵的,我們都知道,利用meltdown漏洞,在用戶空間是可以偷窺到內核空間數據的,下面的應用程式訪問內核地址k:
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~