- C和C++安全編碼(原書第2版)
- (美)Robert C.Seacord
- 2730字
- 2020-10-30 17:56:35
1.3.2 C存在的問題
C是一種靈活、可移植的高級編程語言,它已經廣泛使用逾40年,但在安全社區中它卻是災星。那么C的哪些特性使得用它編程易于犯錯從而導致安全缺陷呢?
C語言的目標是成為一種內存耗用微小的輕量級語言。C的這種特征使得當程序員誤以為某些事情會由C自動處理(而實際上并不會)時,就可能會導致漏洞的出現。如果程序員熟悉某些表面看上去相似的語言,如Java、Pascal或者Ada,那他們更容易誤以為C會為其提供更多的保護。這些錯誤的假設導致程序員容易犯這樣的一些錯誤:對數組的越界不加保護,不處理整數操作的溢出和截斷,以及用錯誤的實參數目調用函數等。
C語言標準的原章節包含一些指導原則。其中,第6點揭示了該語言中絕大多數安全問題的根源:
要點6:保持C精神。C精神的一些方面,可以歸納為下列短語:
(a)信任程序員。
(b)不要阻止程序員做他需要做的東西。
(c)保持語言的小而簡單。
(d)對一種操作只提供一種方法。
(e)即使不能保證可移植性,也要使它快速運行。
箴言(a)和(b)直接違背安全性。在2007年春季的WG14倫敦會議上,代表討論了C11的章節,有觀點認為(a)應修改為“信任與核查”。WG14認為(b)點是C語言持續成功的關鍵。
C標準[ISO/IEC 2011]定義了以下幾種行為。
特定于語言環境的行為(locale-specific behavior):該行為取決于每個實現的文檔記錄的當地國籍慣例、文化和語言。特定于語言環境的行為的一個例子是,對于26個小寫拉丁字母以外的其他字符,islower()函數是否返回true。
未指定行為(unspecified behavior):使用一個未確定的值,或C標準提供兩種或更多可能性的其他行為,并規定,在任何情況下選擇使用哪一種沒有進一步的要求。未指定行為的一個例子是:函數參數的求值順序。
實現定義的行為(implementation-defined behavior):每個實現的文檔記錄如何作出選擇的未指定行為。實現定義行為的一個例子是,當一個有符號整數右移位時高位 [1]的傳播。
未定義行為(undefined behavior):使用一個不可移植的或錯誤的程序構造,或使用錯誤數據,國際標準并沒有規定要求的行為。
附錄J,“可移植性問題”,列舉了這些行為在C語言中的具體例子。
實現是一組特定的軟件,它在一個特定的翻譯環境下運行,具有特定的控制選項,在一個特定的執行環境下執行程序的翻譯,并支持執行其功能。實現基本上是編譯器命令行(包括選定的標志或選項)的同義詞。更改任何標志或選項都可能會導致產生顯著不同的可執行文件,因此實現被看作是單獨的實現。
C標準還介紹了如何確定未定義行為,如下所示。
如果違反在約束之外出現的“應當”或“不得”的規定,那么其行為是未定義的。本國際標準中另有用“未定義行為”一詞指示的未定義行為,或遺漏任何明確定義的行為。特別強調,這三者沒有任何區別,它們都描述“行為是不確定的”。
C標準委員會劃分未定義行為的原因如下:
·為了許可實現者可以不捕獲一些難以診斷的程序錯誤。
·為了避免定義有利于某個實現策略,而不利于另一個實現的不起眼的角落的情況。
·為了找出可能符合語言擴展的區域:實現者可以提供未正式定義行為的定義來增強語言。
合格的實現可以通過各種方式處理未定義行為,如完全無視該情況,并產生不可預知的結果;對環境的特點以一個已記錄的方式翻譯或執行程序(產生或不產生診斷消息);終止翻譯或執行(產生診斷消息)。
未定義的行為非常危險,因為它們不必由編譯器診斷,也因為所產生的程序可能產生任何行為。本書描述的大部分安全漏洞都是在代碼中利用未定義行為的結果。
未定義行為存在的另一個問題是編譯器優化。因為編譯器沒有義務產生未定義行為的代碼,所以這些行為是優化的對象。通過假設未定義行為不會發生,編譯器可以生成具有更好性能特性的代碼。
編譯器的編寫者越來越多地利用C編程語言中的未定義行為來改善優化。通常情況下,這些優化會干擾開發人員對源代碼進行因果分析(也就是說,分析后一階段的結果對前面結果的依賴性)的能力。因此,這些優化消除了軟件中的因果關系,使得產生軟件故障、缺陷和漏洞的概率增加。
正如附錄J的標題所建議的,未指定的、未定義的、實現定義的、特定于語言環境的行為都是可移植性問題。未定義行為是最有問題的,因為它們的行為在一個版本的編譯器中可能良好地定義,但在其后續版本中可能徹底改變。C標準要求,實現記錄并定義所有實現定義、特定語言環境的特點和所有擴展。
從語言的歷史中我們可以看出,在C編程語言的初始階段,可移植性并不是一個主要目標,但當將語言移植到不同的平臺,并最終標準化時,移植性逐漸變得重要。當前的C標準確定了可移植程序的兩個級別:符合(conforming)和嚴格符合(strictly conforming)。
嚴格符合程序只使用那些由C標準規定的語言和庫的功能。嚴格符合程序可以使用有條件的功能,如果這種使用由適當的條件(包含預處理指令)守衛。它不能產生依賴于任何未指定、未定義或實現定義的行為的輸出,也不能超過任何最低執行限制。符合程序是對一個符合標準的實現而言可以接受的程序。嚴格符合程序的目的是要最大限度地在符合的實現之間移植。符合程序可以依賴于一個符合標準的實現不可移植的特性。
可移植性要求,以獨立于底層機器的體系結構的抽象水平對邏輯進行編碼并轉化或編譯成底層的表示。問題出在對這些抽象的語義以及它們如何翻譯成機器級別指令的不精確理解。這種理解的缺失導致了不當的假設、安全缺陷和漏洞。
C語言缺乏類型安全性。類型安全包括兩方面含義:保持性(preservation)和前進性(progress)[Pfenning 2004]。保持性要求如果變量x的類型為t,那么如果x具有值v,則v的類型也為t。前進性要求對一個表達式的計算不會以非預期的方式進行,即要么得到一個值(且計算結束),要么存在某種方式對其進行繼續處理。通俗地說,類型安全就是要求對某特定類型的操作,其結果仍然是原來的類型。C語言起源于兩種無類型的語言,因此仍然保留著很多無類型語言的或弱類型語言的特征。例如,可以通過顯式類型轉換將指向某一類型的指針轉換為指向另一種類型的指針,而當對轉換后的指針進行解引用(dereferenced)時,其行為就是未定義的。還可以用隱式轉換合法地對不同長度的帶符號和不帶符號的數混合操作,并且產生不可表示的結果。這種類型安全的缺乏導致了很大范圍的安全缺陷和漏洞。
出于這些原因,C程序員的責任是開發杜絕未定義行為的代碼,無論是否有編譯器的幫助。
總之,雖然C語言包含了一些經常被誤用,從而導致安全缺陷的因素,但C仍然是一種廣為流行的語言,在許多情況下是多種應用程序的首選語言。這些問題中的部分問題可以通過對語言標準、編譯器以及相關工具等的改進加以解決。短期來看,改善現狀最有效的方式就是通過讓開發人員了解常見的安全缺陷以及相應的緩解策略,教他們如何進行安全的程序設計。從長遠來看,必須對C語言標準、兼容編譯器及庫做進一步的改進,使其繼續作為開發安全系統的可行語言。
- Java程序設計(慕課版)
- C#程序設計實訓指導書
- Three.js開發指南:基于WebGL和HTML5在網頁上渲染3D圖形和動畫(原書第3版)
- Microsoft System Center Orchestrator 2012 R2 Essentials
- Android程序設計基礎
- 低代碼平臺開發實踐:基于React
- Node.js:來一打 C++ 擴展
- Python爬蟲、數據分析與可視化:工具詳解與案例實戰
- C++程序設計教程(第2版)
- Go語言從入門到精通
- Python全棧開發:基礎入門
- Visual C++程序設計全程指南
- 產品架構評估原理與方法
- Java 7 Concurrency Cookbook
- Learning NHibernate 4