快速構建安全的應用程式(二)?

四、C++標準庫中的邊界檢查

  預設情況,C++標準庫中大量的容器物件和迭代物件沒有提供邊界檢查。例如,向量的下標操作符通常是一個比較快,但有潛在的危險性的操作單獨元素的方法。如果你正在尋找得到確認檢查的操作方法,你可以轉向"at"方法。安全性的增加是以犧牲效能為代價的。當然,絕大情況下效能的降低是可以忽略不計的,但是對於效能要求第一位的程式碼來說,這可能是非常有害的,思考一下下面的簡單函式:

void PrintAll(const std::vector & numbers){ for (size_t
index = 0; index < numbers.size(); ++index) {  std::cout <<
numbers[index] << std::endl; }}void PrintN(const
std::vector & numbers, size_t index){ std::cout <<
numbers.at(index) << std::endl;}

  PrintAll函式使用了下標操作符,因為索引由函式控制,並且可以確認是安全的。另一方面,PrintN函式不能保證索引的有效性,所以它使用了更安全的"at"方法來代替。當然,並不是所有的容器的存取操作都象這麼簡潔明瞭。

  在保證C++標準庫的安全特性的同時,Visual
C++2005繼續堅持並在很多情況下改進了C++標準庫的執行特性,同時提供了調節C++標準庫安全性的特色。一項受人歡迎的改進是在除錯版本中添加了範圍檢查,這對你的發行版本效能並不構成影響。但這確實幫助你在除錯階段捕獲越界錯誤,甚至是使用傳統上不安全的下標操作符的程式碼。

  不安全的函式,象vector的下標操作運算元,和其他的函式,象它的front函式,如果不恰當的呼叫,通常會導致不明確的行為。如果你幸運的話,它將很快導致一個存取衝突,這將使你的應用程式崩潰。如果你不那麼走運的話,它可能默默地持續運轉並導致不可預知的副效應,這將破壞資料並可能被進攻者利用。為了保護你的發行版本的應用程式,Visual
C++2005引入了_SECURE_SCL符號,用來給那些非安全的函式新增執行時檢查。象下面的程式碼那樣在你的應用程式中簡單地定義這個符號可以新增額外的實時檢查並阻止不確切的行為。

#define _SECURE_SCL 1

  緊記定義這個符號對你的程式衝擊很大,大量的合法的,但是具有潛在非安全的操作將在編譯時將無法通過,以避免在執行時出現潛在BUG。思考下面的使用Copy運算的例子:

std::copy(first, last, destination);

  其中,first和last是定義拷貝範圍的迭代引數,destination是輸出迭代引數,指示了目標緩衝區的位置,這個位置用來拷貝範圍之內的第一個元素。這裡有一個危險是destination所對應的目標緩衝區或容器不足夠大,無法容納所要拷貝的元素。如果Destination是一個需要安全檢查的迭代引數,類似的錯誤將被捕獲。但是,這僅僅是一個假設。如果destination是一個簡單的指標,將無法保證copy運算函式正確運轉。這時當然會想到_SECURE_SCL符號來避免這一問題,這種情況下,程式碼甚至是不能編譯,以此避免任何可能的執行時錯誤。就象你想象的那樣,這將需要重寫更完美有效的程式碼。所以,這是一個更好的理由支援C++標準庫容器,避免使用C型別陣列。

五、編譯器的安全特點

  雖然對於Visual
C++2005來說並不是全新的,但大量編譯器特色仍然需要了解。與以前版本的顯著區別是編譯器的安全檢查當前預設情況下是開啟的,讓我們來看一下編譯器的特點及在某些情況下它們是如何阻止在某些情況下受到攻擊。

  Visual
C++編譯器很久以前就開始提供嚴格的執行時安全檢查選項,包括棧校驗,下溢和上溢檢查以及未初始化變數的識別。這些執行時檢查由編譯器的/RTC選項來控制。雖然在早期的發展中捕獲錯誤非常有用,但是對於釋出版本效能上的損失卻是不能接受的。微軟的Visual
C++.net引入了/GS編譯開關,對於發行版本來說它添加了有限的執行時安全檢查。/GS開關在編譯開關中插入程式碼,通過檢測函式的棧資料來檢測通常基於棧的緩衝溢位。如果發現問題,應用程式將被終止。為了減少執行時檢查對效能的影響,編譯器辨別哪個函式易於攻擊並且僅針對這些函式來進行安全檢查。安全檢查涉及到在函式的棧框架上增加一個cookie,在緩衝溢位的情況下它將被重寫。函式指令的前後都添加了彙編指令。在函式執行以前,源自cookie模組的函式cookie先執行計算。當函式結束但在棧空間被收回前,cookie的棧拷貝被檢索以判斷它是否被更改。如果cookie未被更改,函式結束並繼續執行程式的下一步,如果cookie被更改了,一個安全錯誤控制代碼將被呼叫,它將結束應用程式。為了在Visual
C++ 2005釋出版本中控制這些編譯選項,開啟工程的屬性頁,單擊C/C++標籤,在程式碼發生屬性頁中,你將發現兩個屬性對應於我剛剛描述的特點。Basic
Runtime Checks屬性對應於開發時/RTC編譯選項,在編譯版本中應設定為"BOTH"。Buffer Security
Check屬性相當於編譯器的/GS選項,對於釋出版本應設定為"YES"。

  對於使用Visual C++
2005的開發人員來說,這些編譯特點在預設情況下開啟,這意味著你可以確信編譯器正在盡其可能阻止你程式碼中的漏洞。然而,這並不意味著我們可以完全不關心安全問題。開發人員需要繼續為正確的程式碼而努力,並且要考慮各種不同的、可能發生的安全威脅。編譯器僅僅可以阻止部分型別的錯誤發生。

  要牢記這些編譯器提供的特殊的安全檢查僅適用於原生代碼,幸運的是,託管程式碼很少犯此類的錯誤。這裡甚至於有更好的訊息,Visual C++
2005引進了C++/CLI設計語言,它提供了.NET框架下最強有力的開發語言。

  六、新的C++程式語言

  Visual C++
2005釋出版本提供了C++/CLI設計語言的一流的實現。C++/CLI是為.NET設計的系統程式語言。相對於其他語言來說,它在建立和使用.NET模組和彙編上有更多的控制。C++/CLI對於C++開發人員來說更精細和自然,無論你是否熟悉C++或.NET框架,你將發現使用C++書寫託管程式碼是對ANSI
C++自然文雅的擴充套件,學習起來非常容易。

  對於開發應用程式來說,有許多強制性的原因讓你來選擇託管程式碼而不是本地C++。兩個最重要的原因是安全性和效率。通用執行時語言(CLR)給你的程式碼提供了一個安全的執行環境。作為一個程式開發人員,你不需要關心緩衝區溢位及因為你在使用前未初始化變數等問題。安全問題沒有完全消失,但是使用託管可以避免通常發生的一些錯誤。

  另外一個使用託管的原因是.NET框架下豐富的類庫。雖然標準C++庫更適合於C++型別程式設計,但是.NET框架包含了一個功能強大的函式庫,這是標準C++庫所無法比擬的。.NET框架包括很多有用的集合類、一個強大的資料操作庫、執行很多流行的通訊協議的類,從SOCKETS到HTTP和網路服務等等。雖然本地C++程式開發人員可以以各種形似使用這些服務,但通過使用.NET框架獲取的生產力主要因為它的統一性和連貫性。無論你是用System::Net::Sockets還是用System::Web名字空間,你將面對同樣的型別,描述廣泛應用的概念,例如流和字串。這是.NET框架具有開發高效率的最主要的原因。這讓程式人員更快速地書寫更強有力的應用程式,同時代碼更可靠。

  Visual C++
2005自然地准許你在一個工程中混合本地與託管程式碼,你可以繼續使用已經存在的本地函式及類的同時,開始使用越來越多的.NET框架下的類庫,甚至是寫你的託管型別。你可以將你的託管型別定義為一個引用型別或一個值型別,雖然Visual
C++編譯器允許你為了方面選擇使用棧語法或是為了控制管理資源使用通常的作用域規則,但值型別在棧上而引用型別位於CLR的託管堆上。

  通過在你定義的class 和 struct
前新增ref來形成一個關鍵詞,定義了一個引用型別。獲取和釋放資源按通常的方式完成,通過使用構造和解構函式,正如這裡說明的:

ref class Connection{ public: Connection(String^
server) {  Server = server;  Console::WriteLine("Aquiring connection
to
server."); } ~Connection() {  Console::WriteLine("Disconnecting
from server."); } property String^ Server;};

  編譯器負責Connection引用型別的IDisposable介面的實現,所以使用類似C#、Visual
Basic.NET的開發人員可以使用任何對他們可用的資源管理結構。對於C++開發人員,有著與以前一樣的選擇。為了簡化資源管理,並書寫"異常"安全程式碼,你可以簡單地在棧上宣告一個Connection物件。當一個物件超過其作用範圍後,執行Dispose方法的解構函式將被呼叫。下面是一個例子:

void UseStackConnection(){ Connection
connection("sample.kennyandkarin.com"); Console::WriteLine("Connection to
{0} established!", connection.Server);}

  這個例子中,通過在函式返回呼叫前呼叫解構函式來關閉這個Connection,這正如你在C++希望的那樣。如果你希望自己控制物件的生命期,僅僅需要使用gcnew這個關鍵詞來獲取connection物件的控制代碼。這個指標可以看作通常的指標(不含有通常的缺陷),並且這個物件的解構函式可以簡單地通過delete操作來呼叫。這個例子程式碼如下

void UseHeapConnection(){ Connection^ connection = gcnew
Connection("sample.kennyandkarin.com"); try {  Console::WriteLine("Connection
to {0}
established!",  connection->Server); } finally {  delete
connection; }}

  正如你所看到的,從本地C++到託管程式碼,Visual C++
2005帶來了簡單靈活的資源管理方式,可以書寫強壯的資源管理程式碼對於書寫正確、安全的程式碼是非常重要的。

  七、小結:

  無論是對於一個小的程式還是一個大的應用,Visual C++
2005釋出版本都是一個功能強大的開發工具,C執行時庫和C++標準庫提供了一個強大的工具集,來發布功能強大的、強壯的本地應用程式,同時,對用C++書寫託管程式碼有著一流的支援,Visual
C++ 2005在微軟的Windows開發平臺上是獨一無二的強大的開發工具。

相關問題答案