完成埠 堆疊溢位 Zephyr使用的堆疊保護技術

2021-10-18 04:54:50 字數 3281 閱讀 2228

zephyr使用k_thread_stack_define建立的執行緒堆疊,所有的執行緒堆疊會在記憶體中依次放在noinit段內。如下圖所示

zephyr建立了a~e五個執行緒相鄰而放,在乙個執行緒堆疊內函式呼叫會占用堆疊,諸如區域性變數,傳遞引數等會放到堆疊內,上圖函式呼叫關係是fun1呼叫fun2,fuc2呼叫fun3。一般情況下堆疊的生長方向是由高位址向低位址(也有特殊的體系架構是反著的),本文只討論高向低生長的情況。基於以上的堆疊結構,堆疊可能出現下面2個問題:

問題1. 執行緒中呼叫的函式層級過深,或者某個函式需要的堆疊很大,會導致堆疊增長超過執行緒分配的堆疊而寫到其它執行緒堆疊中,例如執行緒c中fun3繼續呼叫其它函式一層層下去,堆疊會增長超過c的stack而進入到b中,把b寫壞。

問題2. 函式操作區域性變數發生溢位寫過區域性變數時可能會寫壞函式堆疊,導致出錯。例如fun2的棧幀中有乙個區域性變數a[2],如果對a執行了memcpy超過了2,就可能寫到fun1的棧幀中。那麼fun2返回時會出現錯誤。

針對這兩點zephyr採用了以下技術

軟體保護技術通常不具有」實時」性,也就是說問題發生時不會馬上報錯,要等到特點的檢查點檢查才會報錯。

上下文切換讓出cpu時,檢查被切出線程的堆疊標記

中斷發生時,檢查被中斷執行緒的堆疊標記

執行緒任務函式返回時,檢查該執行緒的堆疊標記

執行緒呼叫k_yield()時,檢查該執行緒的堆疊標記

該技術可檢查」問題2」,配置config_stack_canaries=y後,zephyr對gcc編譯器加入-fstack-protector-all和-mstack-protector-guard=global編譯選項,使用canaries特性對函式堆疊進行檢查,原理如下圖

gcc會在每個函式呼叫前建立乙個標記canaries,函式返回時檢查該標記,如果標記值發生變化說明堆疊被破壞。注意該方法無法檢查所有的函式棧幀破壞情況,要檢查其它情況需要更重量級的工具,但並不適合使用zephyr的小微嵌入式裝置。

該技術是出於安全考慮,執行緒使用堆疊的棧底位址進行偏移,避免非關鍵執行緒通過堆疊溢位的方式攻擊關鍵執行緒,配置config_stack_pointer_random=y後生效,原理如下圖

硬體保護技術具有」實時」性,一旦踩到問題硬體立即報錯。在zephyr中mpu和mmu(mmu會當作mpu來用)的保護是作為通用手段。

當配置config_mpu_stack_guard=y後,當前執行執行緒的最低位址開始減去config_arm_mpu_region_min_align_and_size的區域將被mpu保護起來設定為不可寫(保護的其它thread棧底),如果該執行緒出現了堆疊溢位寫到該區域會觸發mpu exception,如下圖所示

另外不同的體系架構可能會提供其它更高效方便的硬體保護方法,例如會用armv8-m的msplim/psplim暫存器可以直接保護堆疊的邊沿位址,而不用使用mpu。這些內容可以根據實際使用的晶元去了解.

對於問題1的溢位,往往是對執行緒要使用的堆疊評估不足,但堆疊開大了又浪費記憶體。因此zephyr提供了檢查執行緒執行時佔堆疊的方法:在建立執行緒時將堆疊全部寫為0x0a,執行一段時間後(最好是進行全面的覆蓋測試),從堆疊最低位址計算0x0a還有多少位元組這就是執行緒未使用的堆疊,扣除這部分就可以得到執行緒可能使用堆疊的最大值,原理如下圖

除了在執行**估最大堆疊使用,zephyr在配置config_stack_usage=y後,編譯時會加入-fstack-usage選項,gcc會在編譯時計算每個函式占用的堆疊大小,通過人工或者指令碼將執行緒全面呼叫關係結合函式堆疊的大小就可以計算執行緒將會使用堆疊的最大值。但**中如果使用了三方庫例如newlib內的函式,**沒有參與編譯因此就無法評估其內部函式使用堆疊大小。當加入-fstack-usage編譯完成後每個原始檔都會對應的產生乙個su檔案,例如thread.c會產生乙個thread.c.su,內容如下

1234567891011121314151617181920212223

thread.c:37:6:k_thread_foreach	0	staticthread.c:59:6:k_thread_foreach_unlocked	0	staticthread.c:77:6:k_is_in_isr	0	staticthread.c:86:6:z_thread_essential_set	0	staticthread.c:96:6:z_thread_essential_clear	0	staticthread.c:106:6:z_is_thread_essential	0	staticthread.c:112:6:z_impl_k_busy_wait	16	staticthread.c:201:5:z_impl_k_thread_name_set	0	staticthread.c:248:13:k_thread_name_get	0	staticthread.c:258:5:z_impl_k_thread_name_copy	0	staticthread.c:271:13:k_thread_state_str	0	staticthread.c:371:6:z_impl_k_thread_start	8	staticthread.c:387:13:schedule_new_thread	8	staticthread.c:493:6:z_setup_new_thread	40	staticthread.c:589:9:z_impl_k_thread_create	40	staticthread.c:677:6:z_impl_k_thread_suspend	16	staticthread.c:699:6:z_thread_single_resume	8	staticthread.c:705:6:z_impl_k_thread_resume	8	staticthread.c:737:6:z_init_static_threads	32	staticthread.c:780:6:z_init_thread_base	0	staticthread.c:443:6:z_new_thread_init	16	staticthread.c:801:20:k_thread_user_mode_enter	8	staticthread.c:851:5:z_impl_k_float_disable	0	static
字段含義:

堆疊的緩衝區溢位

我們在做專案的過程中遇到過乙個問題 開啟底層板卡,對板卡進行讀寫的時候,板卡的控制代碼莫名奇妙改變了,而我們沒有顯式改變過它。其實這是一類問題,就是區域性變數莫名其妙被修改。這是由於堆疊的緩衝區溢位造成的,主要現象是 1.某些區域性變數莫名其妙被改 2.函式返回的時候崩潰 主要原因是 1.陣列越界 ...

Linux堆疊溢位的經典問題

看了一下,linux一站式學習的函式呼叫 搞的我熱血沸騰,立馬找你一下這方面的資料看看。終於看到了乙個有關linux堆疊溢位的文章,就拿來試試手了。參考 我的學習linux筆記 一 堆疊 下面來貼一下我的 吧。基本和上篇文章一樣的。c sharp view plain copy include vo...

堆疊的使用

題目描述 堆疊是一種基本的資料結構。堆疊具有兩種基本操作方式,push 和 pop。push乙個值會將其壓入棧頂,而 pop 則會將棧頂的值彈出。現在我們就來驗證一下堆疊的使用。輸入 對於每組測試資料,第一行是乙個正整數 n,0 輸出 對於每組測試資料,根據其中的命令字元來處理堆疊 並對所有的 a ...