一、Programming Languages體系
靜態程式分析是程式語言中應用層面下的一個細分領域,它是一個非常重要的核心內容。
- 在理論部分,考慮的是如何設計一個語言的語法和語義,如何設計語言的型別系統等等問題。在過去十年中,語言核心幾乎沒有變化
- 有了語言的語法、語義和型別系統之後,我們需要支撐語言的執行。因此,在環境部分,需要考慮如何為執行中的程式提供執行時環境——如何設計編譯器,在執行時需要怎樣的支援(如記憶體的分配管理)等等。語言承載環境處於一個緩慢提升的階段,主要集中在硬體裝置以及高效能程式設計優化方面
- 變化最大的是程式分析,因為隨著IT、雲端計算、軟體SaaS的快速發展,軟體的規模變得更大、結構更復雜、數量更多。如何確保系統的可靠性、安全性和其他承諾,如何自動合成一個程度,成為了一個日趨熱門的研究和工程化領域
二、Static Analysis定義
Static analysis analyzes a program P to reason about its behaviors and determines whether it satisfies some properties before running P.
- Does P contain any private information leaks?
- Does P dereference any null pointers?
- Are all the cast operations in P safe?
- Can v1 and v2 in P point to the same memory location?
- Will certain assert statements in P fail?
- Is this piece of code in P dead (so that it could be eliminated)?
- …
上圖中的兩種答案在靜態分析語義中都是對的,他們分別代表了兩種求解方式:
- 分支窮舉:耗時,但是精確
- 符號執行:快速,但是不那麼精確
Static Analysis: ensure (or get close to) soundness, while making good trade-offs between analysis precision and analysis speed.
Two Words to Conclude Static Analysis:
Static Analysis = Abstraction + Over-approximation
舉一個具體的例子:通過靜態分析,判斷一段PHP程式碼是否能存在外部任意引數執行風險,即是否是Webshell。要完成這個靜態分析過程,需要進行如下處理:
- Abstraction
- Over-approximation
- Transfer functions
- Control flows
原始程式碼如下:
<?php v1 = 1; v2 = 2; v3 = $_POST[1]; v4 = $_POST[2]; v5 = v3 == 1 ? v3 : 5; $$_POST[3] = $_POST[4]; // $_POST[4] = v6 if(v3 == 1){ eval(v5); } echo "hello world"; ?>
我們先來看Abstraction抽象,
通俗地理解Abstraction抽象,就是將程式從原始的、高維的原始碼空間,對映到一個抽象的、低維的符號空間。符號化後,後續的優化、分析、處理都會更加方便。
接下來看Over-approximation: Transfer Functions轉化函式,
- In static analysis, transfer functions define how to evaluate different program statements on abstract values.
- Transfer functions are defined according to “analysis problem” and the “semantics” of different program statements.
轉化函式定義了抽象符號的運算結果,需要注意的是,轉換函式和具體的語義和待分析問題有關.
需要注意的是,因為在Abstraction抽象過程中進行了值域空間的降維抽象,所以在轉換函式對映中,靜態符號執行和動態實際實行的結果之間,是存在差異的,這是不可避免的。
接下來看Over-approximation: Control Flows控制流,
As it’s impossible to enumerate all paths in practice, flow merging (as a way of over-approximation) is taken for granted in most static analyses.
在靜態分析中,分支流合併是常用的分支推斷技術,提升了Soundness的同時,也導致Completeness的下降,從而導致了不可避免的誤報問題。
三、Why we need Static Analysis
- 近年來,程式複雜度越來越高,可靠性和安全性越來越難保證
- Null pointer dereference, memory leak, etc.
- 空指標引用與記憶體洩漏等:幾乎每個程式編寫者都被這兩個問題所困擾過
- 對程式可靠性、安全性進行分析
- Private information leak, injection attack, etc.
- 隱私資訊洩漏:這一問題在移動應用中較為普遍
- 注入攻擊:這是網路安全中非常常見的議題
- 為編譯優化提供基礎技術
- Dead code elimination, code motion, etc.
- 死程式碼消除:在編譯器的機器無關優化環節,將不會對程式執行結果產生影響的程式碼(即死程式碼)刪除。
- 迴圈不變數的程式碼移動:在編譯器的機器無關優化環節,在保證不影響程式執行結果的情況下,將迴圈中的特定語句移動到迴圈外,使得程式執行時執行的語句數減少。更為詳細的解釋可以參考StackOverFlow上的回答。
- 程式理解(呼叫棧、自動補全等)
- IDE call hierarchy, type indication, etc.
- 為整合開發環境的功能提供幫助:當你使用VS/Idea/Clion/Eclipse/Android Studio等等IDE時,將滑鼠懸停在程式碼上,IDE能夠動態地分析並提示你所懸停物件的相關資訊,背後使用的技術就是靜態程式分析。
- 更深入地理解程式語言的語法語義
- 自然地寫出更可靠、安全、高效的程式
四、Rice’s Theorem -- 靜態分析的侷限
Any non-trivial property of the behavior of programs in a r.e.(recursively enumerable) language is undecidable.”
non-trivial properties:
- ~= interesting properties
- ~= the properties related with run-time behaviors of programs
按照萊斯定律,「完美靜態分析」有兩個核心特徵:
- Sound(完全覆蓋)
- Complete(精確推斷)
如果一段程式是“non-trivial”的,則不存在一個完美的靜態分析程式,可以同時滿足Sound和Complete特徵。換到工業界的術語就是,誤報和漏報無法同時達到100%。
在實際使用中,我們並不是追求「完美靜態分析」,而是追求「有用的靜態分析」,即滿足如下兩個核心特徵:
- Compromise soundness (false negatives,折中地漏報控制)
- Compromise completeness (false positives,折中地誤報控制)
在實際工業場景中,Soundness往往是優先追求的目標,我們以Webshell靜態程式碼分析為例說明。
如果追求Sound的目標,在進行靜態程式碼分析的時候,完整性/覆蓋度/高檢出往往是優先追求的目標。在另一方面,相對的誤報就不可避免了。
五、靜態程式分析與類似技術的對比
靜態程式分析
- 優點:
- 在選定的精度下能夠保證沒有bug
- 缺點:
- 學術門檻相對高。目前已知國內高校公開的課程資料只有北京大學,南京大學,國防科大,吉林大學的,且通俗易懂的教材稀少。作為一門計算機專業的高年級選修課,入門和提高都較困難。
動態軟體測試
- 優點:
- 在工程中被廣泛應用,並且有效。實現簡單,便於自動化。
- 缺點:
- 無法保證沒有bug。 這是無法遍歷所有可能的程式輸入的必然結果。
- 在當今的由多核與網路應用帶來的併發環境下作用有限。 某個bug可能只在特定情況下發生,因而難以穩定地復現。
形式化語義驗證
- 優點:
- 由於用數學的方法對程式做了抽象,能夠保證沒有bug。
- 缺點:
- 學術門檻較高,學習者必須有良好的數學基礎才能入門。
- 驗證代價較高,一般來說非常重要的專案會使用這一方式保證程式質量。甚至在作業系統這樣重要的軟體中,也並不一定會使用。