參加本次軟件可靠性與安全性高級技術研討會學習主要的收獲是學習了對軟件可靠性與安全性設計與實現的方法,將會在以后在軟件設計的工作中提供重要的幫助。現將軟件可靠性與安全性設計與實現的知識點進行梳理記錄。
1. 防錯性設計與實現
防錯性設計包括:簡化設計、算法與數據管理、慎用易錯架構、使用監錯技術、多任務管理。
1.1. 簡化設計
復雜性是可靠性最凶惡的對手,復雜的軟件會導致:代碼規模更大、缺陷更多;交互關系更多、缺陷更多;更難測試、不充分的可能性更大;設計、實現、配置、使用的難度更大;用戶更難理解。
1.1.1. 控制模塊的復雜性
1) 單元的理論最佳長度66到132行
2) 清晰定義模塊的所有輸入輸出並進行范圍檢測(架構設計)
3) 模塊有唯一的入口和出口
4) 模塊中的循環有正常的退出條件
5) 保持模塊的控制流從頂到底
6) 盡量降低模塊的圈復雜度(不大於10)
1.1.2. 控制軟件的復雜性
1) 強內聚
內聚性指模塊相對功能密度的度量,依賴於一個單元中各種操作之間互相聯系的緊密程度。

a) 功能內聚

b) 順序內聚

c) 通訊內聚

d) 過程內聚

e) 不可取:時間內聚,邏輯內聚
2) 松耦合
耦合性指兩個模塊之間聯系的緊密程度,依賴於模塊間接口的復雜性、引用或進入模塊的點、通過接口傳遞的數據。

a) 簡單數據耦合

b) 數據結構耦合

c) 不可取:控制耦合,公用耦合,內容耦合



3) 扇入扇出
模塊的扇出指模塊的直屬下層模塊的個數,模塊的扇入指有多少個上級模塊調用本模塊。
a) 上層模塊高扇出
b) 下層模塊高扇入
c) 單元調用嵌套層數不大於7
d) 扇入扇出數不大於7
1.2. 算法與數據管理
1.2.1. 算法選擇

1.2.2. 數據管理
1) 參數化
在軟件設計中,用統一的符號來表示參數、常量和標志, 以便在不改變源程序邏輯的情況下,對它們進行修改。
2) 尋址模式的選用
盡量不使用間接尋址方式,在確實有必要采用間接尋址方式時,慎重考慮和充分論證,並在執行之前驗證地址是否在可接受的范圍內。
3) 文件
文件必須唯一且用於單一目的;文件在使用前必須成功地打開,在使用結束必須成功地關閉;文件的屬性應與對它的使用相一致。
4) 對關鍵下標, 在使用前進行范圍檢查
5) 使用數據前, 應采取措施保證所有所需的數據都是可用的
6) 應采取措施, 對關鍵數據進行保護
7) 對數據的非法組合進行檢查
1.3. 慎用易錯架構
典型易錯架構
| 浮點數 |
指針 |
| 遞歸 |
中斷 |
| 繼承 |
別名 |
| 無界數組 |
動態內存分配 |
| 全局變量 |
公用數據和公共變量 |
| 不檢查輸入參數長度的庫函數 |
|
1) 浮點數
先天不精確,有可能導致無效的比較;增加浮點協處理器和內嵌算法/庫函數確認的工作量。
2) 指針
指針引用錯誤的內存區域可能導致數據誤用。
3) 遞歸
錯誤的遞歸容易導致內存溢出;當使用遞歸時, 應有明顯的判據, 可預測遞歸的深度。
4) 中斷
有可能導致關鍵操作的終止;使程序難以理解,類似 goto 語句;使用時,應仔細考慮寄存器和共享變量的內容、中斷優先級、中斷發生的時機、中斷發生的最大可能頻率、中斷處理時間等。
5) 繼承
代碼非局部化,代碼的修改可能導致無法預期行為,產生難以理解的問題。
6) 別名
使用多個變量名來訪問相同狀態變量,會使程序的理解和修改變得困難。
7) 無界數組
如果不進行任何數組邊界檢查,可能出現緩沖區溢出失效。
8) 動態內存分配
在有些場景下,軟件運行時內存塊大小不能在代碼編譯時確定, 需要根據代碼的運行環境來確定;軟件執行過程中, 根據需要分配或者回收存儲空間;在C/C++程序中,應正確使用malloc、calloc、realloc、new、alloca與free、delete管理動態內存。
不當動態內存分配的后果:內存泄露、內存碎片。
內存泄露的原因:忘記了回收;回收前失去了對內存的追蹤,(如:存儲指針值的變量被移出了作用域、指針值被重寫、沒有保存地址指針);庫函數存在內存泄露缺陷,對庫函數接口的誤解。
9) 全局變量
全局變量不好控制;不利於程序的結構化;不用或少用全局變量
10) 公用數據和公共變量
公用數據和公共變量指明由兩個或多個模塊公用的數據和公共變量。盡量減少對公共變量的改變,以減少模塊間的副作用。
11) 慎用不檢查輸入參數長度的庫函數
緩沖區溢出漏洞;這些函數直接把輸入參數的內容復制到緩沖區中,只要輸入參數長度大於緩沖區長度,就會造成緩沖區溢出,使程序運行出錯;如strcpy()、strcat()、sprintf()、vsprintf()、gets()、scanf()等
1.4. 使用監錯技術
使用條件判斷

在開發和維護階段,使用監錯技術提示:相互矛盾的假設、傳入程序的不良數值等。
主要的監錯技術:
1) 斷言
斷言是一個在假設不正確時會預警的函數或宏指令,可使用斷言監錯。在開發階段,斷言可以提示相互矛盾的假設、傳入程序的不良數值等等;在維護階段,斷言可以表明改動是否影響到了程序其它部分。
例子:
assert(y != 0);
int z = x / y;
2) 異常情況處理
異常是在運行時發生的,無法在設計時預料到的“非常”事件,這種事件通常和具體的運行環境和資源分配有關。仔細分析軟件運行過程中各種可能的異常情況,預先設計相應的保護措施,或者利用異常處理做一些必須的善后工作。在開發階段,可以利用異常情況處理,產生一個警告,提示出現了異常情況,使異常情況的出現變得非常明顯;在運行階段, 異常處理措施應該能使出現的異常情況可以得到修復。
1.5. 多任務管理
多任務設計是軟件應用的新趨勢,但是多任務之間可能存在難以預知的交互,導致同步錯誤。
多任務設計的原則:
1) 注意函數的可重入性
不為連續的調用持有靜態數據;不返回指向靜態數據的指針,所有數據都由函數的調用者提供;使用本地數據,或者通過制作全局數據的本地拷貝來保護全局數據;如果必須訪問全局變量,記住利用互斥信號量來保護全局變量;不調用任何不可重入函數。
2) 避免死鎖與活鎖
當兩個或者更多的進程停下來相互等待對方完成某個動作時,就造成了死鎖,通常表現為系統掛起。活鎖於死鎖類似,只是當前系統仍然能夠進行一些計算,但永遠無法轉到其它狀態。死鎖與活鎖的發生常常是因為很難預期和重現的罕見的條件組合。
3) 避免臨界點競爭
2. 健壯性設計
軟件健壯性(Robustness)指軟件系統在遭遇異常的情況下,仍然能夠正常且安全運行的能力。主要關注外部異常。
異常情況舉例:相連的軟硬件系統發生了故障或性能降級、輸入錯誤、有意的攻擊、其他非正常情況。
2.1. 硬件失效
軟件設計必須考慮所涉及的硬件潛在失效模式。
典型考慮:
1) 電源失效防護
2) 電磁干擾
3) 系統不穩定
4) 干擾信號
2.2. 接口考慮
2.2.1. 人機接口設計
可選擇的常用方法:
1) 接受錯誤輸入,留給系統處理
2) 接受錯誤輸入, 什么也不產生
3) 接受錯誤輸入, 輸出錯誤提示信息
4) 不允許錯誤輸入進入
較完善的設計:
1) 軟件能判斷出操作員的輸入操作正確性(或合理性)
2) 在遇到不正確(或不合理)輸入和操作時, 軟件拒絕該操作的執行
3) 軟件提醒操作員注意錯誤的輸入或操作
4) 軟件指出錯誤的類型和糾正措施
2.2.2. 程序接口設計
對輸入參數進行合法性檢查,對非法參數進行處理,常用方法:
1) 返回錯誤代碼
2) 返回中間值
3) 使用下一個合法數據代替
4) 使用上一個合法數據代替
5) 使用最接近的合法值
6) 調用異常處理程序進行處理
7) 調用顯示錯誤信息程序並打印出來
8) 關閉程序
2.2.3. 硬件接口
硬件接口的軟件設計必須考慮:
1) 使用握手信號保證通信的連通性
2) 預先確定數據傳輸信息的格式和內容
3) 每次傳輸都包含一個字或字符串來指明數據類型及信息內容
4) 使用奇偶校驗、循環冗余校驗(CRC校驗)、海明碼來驗證數據傳輸的正確性
5) 充分估計接口的各種可能故障,並設計相應的處理措施
6) 對非法的外部中斷的處理,軟件應能夠識別合法的及非法的外部中斷
7) 對傳感器故障的考慮
反饋回路中的傳感器有可能出現故障並導致反饋異常信息, 軟件應能預防將異常信息當作正常信息處理而造成反饋系統的失控。
8) 對輸入/輸出信息的考慮
軟件對輸入、輸出信息進行加工處理前,應檢驗其是否合理(最簡單的方法是極限量程檢驗)。對不合理的輸入進行正確的處理。通過設計,保證輸入/輸出符合精度要求。
3. 容錯性設計
3.1. 容錯策略
容錯是指在發生故障的情況下,系統不失效,仍然能夠正常工作的特性。容錯軟件:在一定程度上,對自身故障具有屏蔽能力;在一定程度上, 能從錯誤狀態自動恢復到正常狀態;因缺陷而發生故障時, 仍然能在一定程度上完成預期的功能。
1) 故障探測
故障(不正確的系統狀態)發生時, 系統必須能夠探測到
2) 危害診斷
必須弄清楚受故障影響的系統范圍
3) 故障恢復
系統必須恢復到已知的安全狀態
4) 故障修復
可以對系統進行改進, 防止故障再次發生
3.2. 容錯技術
| 級別 |
說明 |
| 0級 |
沒有任何容錯 |
| 1級 |
自動檢測並重新啟動 |
| 2級 |
在1級的基礎上, 增加周期性檢查點, 記錄和恢復內部狀態 |
| 3級 |
在2級的基礎上, 增加持續穩固的數據恢復 |
| 4級 |
無中斷的持續運行 |
3.2.1. 監控定時器
提供監控定時器或類似措施,以確保微處理器或計算機具有處理程序超時或死循環故障的能力。
監控定時器的設計原則:
1) 監控定時器應力求采用獨立的時鍾源, 用獨立的硬件實現
2) 采用可編程定時器實現時, 應統籌設計計數時鍾頻率和定時參數, 力求在外界干擾條件下, 定時器受到干擾后, 定時參數的最小值大於系統重新初始化所需的時間值, 最大值小於系統允許的最長故障處理時間值
3) 硬件狀態變化有關的程序設計應考慮狀態檢測的次數或時間, 無時間依據的情況下可用循環等待次數作為依據, 超過一定次數作為超時處理
3.2.2. 冗余設計
1) 空間冗余
在某一運行空間出現問題時, 可以啟用另外的空間來工作。典型的空間冗余:存儲空間冗余(RAID,數據庫日志等)、處理器空間冗余(多個CPU協同工作)、網絡空間冗余、進程冗余(Apache服務器)。
2) 時間冗余
為了獲得成功的結果, 多次嘗試相同的操作。典型的時間冗余:外部接口數據傳輸、傳感器數據的采集、磁盤數據的讀取、重啟不穩定的服務。
3) 結構冗余
結構冗余的典型方式為TMR(Triple-Modular Redundancy),TMR能成功地容錯基於兩個基本假設:組件不能包含相同的設計缺陷;組件的失效是隨機的,多個組件同時失效的概率極低。對於相同的軟件而言,兩個假設都無法滿足:簡單復制的軟件將包含相同的設計缺陷;對於相同的軟件,同時失效不可避免。用於容錯的軟件必須相異。
軟件結構冗余技術:
a) N版本程序設計
不同的小組用許多變體實現相同的規格說明,所有變體同時進行計算,利用表決系統選擇多數作為輸出。假定不同的小組犯相同錯誤的概率極低,也可能連算法都不一樣。N版本程序設計是最常用方法,例如: 在許多型號空中客車商用飛機中。

b) 恢復塊技術
相同的規格說明被實現成若干個明確不同的版本 順序執行。利用驗收檢測程序選擇接受的輸出。強制每個版本使用不同的算法, 以降低相同錯誤的概率。驗收檢測程序的設計困難,必須獨立於所使用的計算。由於冗余版本是依次順序執行的,用於實時系統時應注意。

c) 一致性恢復塊

d) 驗收表決

e) 多版本軟件開發策略
保證冗余軟件版本之間的設計相異性;保證各獨立單元具有高可靠性;保證表決算法具有高可靠性;保證驗收檢測程序具有高精確度和高可靠性
f) 相異性設計
用不同的途徑設計和實現不同的軟件版本, 使各版本具有不同的失效模式, 降低各版本包含一致性缺陷的概率。
不同的設計方法:面向對象/面向功能的設計、用不同的程序設計語言實現、使用不同的工具和開發環境、使用不同的實現算法。
4. 安全性設計
4.1. 准則
1) 確保最薄弱環節的安全
2) 縱深防御,避免單點失效
3) 失效安全:進入安全狀態、阻止信息非授權訪問
4) 最小化准則:范圍最小化、權限最小化
5) 通過冗余和多樣性降低風險
6) 驗證所有輸入
4.2. 方法
4.2.1. 風險隔離
划分(Partitioning):為功能上獨立的軟件部件提供隔離的過程。
使用划分的目的:抑制故障的影響和/或隔離故障,防止組件之間特殊的相互作用和交叉耦合干擾;減少軟件驗證過程的工作量;最小化安全相關組件的規模。
划分等級的確定:對於通過划分提供保護的軟件,可使用與每個組件相關的最嚴重的失效狀態類別來確定該組件的重要等級;區別對待不同等級的軟件組件。
划分時考慮的因素:
1) 硬件資源,包括處理器、存儲器設備、輸入/輸出設備、中斷和定時器
2) 控制耦合性,外部存取脆弱性
3) 數據耦合性,共享或覆蓋數據,包括堆棧和處理器寄存器
4) 與保護機制相關的硬件設備的失效模式
運用信息隱蔽技術,使信息僅對有權和需要訪問它的程序開放。信息隱蔽可以避錯的三個理由:降低了信息意外訛誤的概率;可以幫助在程序中建立防火牆,降低信息問題影響的范圍;由於信息被局部化,程序員更少地產生錯誤,驗證人員更容易找到缺陷。
常見需要隔離的信息:
1) 安全關鍵的數據
2) 容易被改動的區域
3) 復雜的數據
4) 復雜的邏輯
5) 在編程語言層次上的操作
信息隱蔽的障礙:
1) 信息過度分散
2) 交叉依賴
3) 誤把局部數據當成全局數據
4) 誤認為會損失性能
在數據區和指令區建立防火牆:
1) 為了防止程序把數據錯當指令來執行,要采用將數據與指令分隔存放的措施
2) 將不用的內存區域初始化成具有確定性的模式,阻止程序意外跳轉到未知存儲中運行,一旦不用的內存區域被當做指令被執行,應使系統恢復到已知的安全狀態
3) 必要時,在數據區和表格的前后加入適當的NOP指令和跳轉指令,使NOP指令的總長度等於最長指令的長度, 然后加入一條跳轉指令,將控制轉向出錯處理程序
4.2.2. 異常處理
- 內部異常處理
1) 在運行階段, 對於預期范圍內的異常, 異常處理措施應保證系統處於安全狀態, 並持續運行
2) 在運行階段, 對於超出預期的異常, 異常處理措施最低限度應使系統轉入安全狀態
3) 在異常發生之后, 采取措施, 保證數據的完整性
4) 在異常發生之后, 采取措施, 保證敏感和關鍵數據不被泄露
- 外部異常處理
1) 周期性檢測外部輸入/輸出設備的狀態, 並在發生失效時轉到到某個安全狀態
2) 對於非法的外部中斷, 軟件應能自動切換到安全狀態
4.2.3. BIT (Built-In self-Test)
BIT是指系統、設備內部提供的檢測故障、隔離故障的自動測試能力,是改善系統、設備安全性、測試性、維護性、可用性的技術。BIT增加了系統復雜性,自身的可靠性也非常重要。BIT向PHM方向發展。
軟件設計必須考慮在系統加電時完成系統級的自檢測, 驗證系統是安全可靠的, 並在正常地起作用。在可能時, 軟件應對系統進行周期性自檢測, 以監視系統的安全狀態。必要時, 軟件應提供維護自檢測, 提高系統的可維護性和可測試性。加電自檢測、周期性自檢測、維護自檢測應具有不同級別的檢測能力。
應注意周期性自檢測功能對其它任務的影響。建立一個可能影響軟件的硬件失效列表, 必須清楚地知道並文檔化可以通過BIT進行檢測的故障模式。BIT發現故障時, 應以顯式的方式報告給需要知曉的人, 尤其是哪些安全相關的故障。
1) CPU自檢測
EMI、放電、電擊、宇宙射線等都可能損壞CPU, 通常在引導時進行CPU自檢, 以便證實CPU的運算正確, 如果測試失敗, 則CPU有缺陷, 軟件應進入安全狀態
2) ROM檢測
ROM(EEPROM或閃存等)上的軟件會優先執行, 檢驗其完整性是重要的, 通常在上電時完成檢測, 如果系統具有變更自身程序的能力則檢測應該周期性運行
4.2.4. 故障監控
應設計監控功能, 直接檢測可能引起的失效狀態, 並對危險失效采取安全防護。監控功能應確保想要檢測的故障在所有必要的條件下都能得到檢測。應確保監控功能和防護措施不會因為導致被監控危害的同一失效狀態而不予動作(共因失效)。

4.2.5. 部署設計
軟件設計應提供內嵌的對部署的支持, 降低系統管理員或用戶配置軟件過程中出現錯誤。
1) 提供對配置的檢查和分析的功能
2) 最小化缺省特權
3) 局部化配置設定
4) 提供修補安全漏洞的簡單方法
