1 前言
openssl 開源庫作為 C/C++ 項目中常用的組件庫,截至 2025年7月4日 ,openssl 的提交記錄包含 119 個 Fix memory leak
。
本文基于源碼 Commit 分析,揭示了 OpenSSL 內存泄漏修復從被動應對到主動防御的演進趨勢,給各位 C/C++ 開發者提供了從漏洞分類到防御實踐的完整參考。
2 內存泄漏類型分類
1. 動態內存分配未釋放
- 場景:使用malloc/new分配內存后未調用free/delete釋放
- Commit ID示例:
- 6543f34:PKCS7_add_signed_attribute失敗時未釋放seq指針
- 20a2f3b:ecdsa_keygen_knownanswer_test未釋放EC_POINT對象
- d48874a:sk_OPENSSL_STRING_push返回失敗時未釋放字符串數組
示例代碼 DIFF:
- return PKCS7_add_signed_attribute(si, NID_id_smime_aa_signingCertificate,
- V_ASN1_SEQUENCE, seq);
+ if (!PKCS7_add_signed_attribute(si, NID_id_smime_aa_signingCertificate,
+ V_ASN1_SEQUENCE, seq)) {
+ ASN1_STRING_free(seq);
+ return 0;
+ }
+ return 1;
- 發現方式:人工代碼審查、單元測試
2. 資源句柄未關閉
- 場景:BIO對象、SSL會話、文件句柄等資源未正確釋放
- Commit ID示例:
- 857c223:load_key_certs_crls使用stdin時未關閉BIO對象
- 9561e2a:PSK會話文件加載后未釋放會話資源
- 35b1a43:tls_decrypt_ticket未釋放HMAC_CTX和EVP_CIPHER_CTX
示例代碼 DIFF:
- return PKCS7_add_signed_attribute(si, NID_id_smime_aa_signingCertificate,
- V_ASN1_SEQUENCE, seq);
+ if (!PKCS7_add_signed_attribute(si, NID_id_smime_aa_signingCertificate,
+ V_ASN1_SEQUENCE, seq)) {
+ ASN1_STRING_free(seq);
+ return 0;
+ }
+ return 1;
- 發現方式:Valgrind內存分析、功能測試
3. 容器/棧內存泄漏
- 場景:數組、鏈表等容器結構在錯誤路徑未清空
- Commit ID示例:
- 1c42216:crl2pkcs7工具錯誤路徑未釋放OPENSSL_STRING棧
- 55500ea:X509_verify_cert未釋放證書擴展數據棧
- 4798e06:X509V3_add1_i2d未釋放證書擴展對象
示例代碼 DIFF:
bio = BIO_new_fp(stdin, 0);
- if (bio != NULL)
+ if (bio != NULL) {ctx = OSSL_STORE_attach(bio, "file", libctx, propq,get_ui_method(), &uidata, NULL, NULL);
+ BIO_free(bio);
+ }
- 發現方式:Coverity靜態分析、集成測試
4. 跨模塊資源泄漏
- 場景:引擎(ENGINE)、密碼學模塊間資源未協調釋放
- Commit ID示例:
- 8c63b14:engine_cleanup_add_first未釋放引擎輔助結構
- a076951:X509_PUBKEY_set創建新密鑰后未釋放舊資源
- 3a1d2b5:i2d_ASN1_bio_stream未釋放ASN.1編解碼狀態數據
示例代碼 DIFF:
item = int_cleanup_item(cb);
- if (item)
- sk_ENGINE_CLEANUP_ITEM_insert(cleanup_stack, item, 0);
+ if (item != NULL)
+ if (sk_ENGINE_CLEANUP_ITEM_insert(cleanup_stack, item, 0) <= 0)
+ OPENSSL_free(item);
- 發現方式:模塊間接口測試、人工代碼審查
5. 異常處理路徑泄漏
- 場景:函數在異常/錯誤返回時未執行清理邏輯
- Commit ID示例:
- 009fa4f:test_evp_cipher_pipeline錯誤時未釋放密碼學上下文
- fa856b0:copy_issuer中sk_GENERAL_NAME_reserve失敗未釋放ialt
- b2474b2:tls_parse_ctos_psk錯誤路徑未釋放SSL_SESSION
示例代碼 DIFF:
if (sesstmp == NULL) {SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
- return 0;
+ goto err;
}
SSL_SESSION_free(sess);
sess = sesstmp;
- 發現方式:錯誤注入測試、AddressSanitizer
3 測試與驗證方式分類
1. 靜態分析工具
- 工具:Coverity、Clang Scan-Build
- Commit ID示例:
- 009fa4f:Coverity檢測到test_evp_cipher_pipeline泄漏
- 33c4187:Coverity發現apps/cmp.c資源泄漏
- 8515534:Coverity報告crltest錯誤路徑泄漏
示例代碼 DIFF:
- aint = ASN1_INTEGER_new();
- if (aint == NULL || !ASN1_INTEGER_set(aint, value))
+ if ((aint = ASN1_INTEGER_new()) == NULL)goto oom;val = ASN1_TYPE_new();
- if (val == NULL) {
+ if (!ASN1_INTEGER_set(aint, value) || val == NULL) {ASN1_INTEGER_free(aint);goto oom;}
@@ -2065,6 +2064,7 @@ static int handle_opt_geninfo(OSSL_CMP_CTX *ctx)return 1;oom:
+ ASN1_OBJECT_free(type);CMP_err("out of memory");return 0;}
- 特點:可發現代碼邏輯缺陷,提前預防泄漏
2. 動態內存檢測
- 工具:AddressSanitizer(ASan)、Valgrind
- Commit ID示例:
- a906436:ASan檢測到x509_req_test中53字節泄漏
- 6b5c7ef:ASan發現TLS1.2壓縮導致35KB泄漏
- 9c7a780:Valgrind定位到內存泄漏報告未釋放bio_err
示例代碼 DIFF:
@@ -435,6 +431,10 @@ int main(int Argc, char *ARGV[])#endifapps_shutdown();CRYPTO_mem_leaks(bio_err);
+ if (bio_err != NULL) {
+ BIO_free(bio_err);
+ bio_err = NULL;
+ }OPENSSL_EXIT(ret);}
- 特點:運行時捕獲實際泄漏,定位具體分配位置
3. 單元與功能測試
- 場景:密鑰生成、證書驗證、加密解密等測試用例
- Commit ID示例:
- 21f0b80:ssl_old_test.c中TLS密鑰協商測試泄漏
- 81d61a6:ectest橢圓曲線測試未釋放臨時密鑰
- c5d0612:asynctest異步測試未調用清理函數
- 發現方式:測試覆蓋率分析、持續集成(CI)
4. 模糊測試(Fuzzing)
- 工具:libFuzzer、oss-fuzz
- Commit ID示例:
- 6afef8b:libFuzzer發現無效CertificateRequest泄漏
- 1400f01:libFuzzer檢測到ASN.1編解碼泄漏
- a1d6a0b:oss-fuzz發現tls_parse_stoc_key_share泄漏
示例代碼 DIFF:
@@ -1027,6 +1027,7 @@ int tls_parse_stoc_key_share(SSL *s, PACKET *pkt, int *al)PACKET_remaining(&encoded_pt))) {*al = SSL_AD_DECODE_ERROR;SSLerr(SSL_F_TLS_PARSE_STOC_KEY_SHARE, SSL_R_BAD_ECPOINT);
+ EVP_PKEY_free(skey);return 0;}
- 特點:通過異常輸入觸發邊緣場景泄漏
4 防御與修復措施分類
1. 錯誤處理優化
- 措施:統一使用goto err模式,確保所有路徑釋放資源
- Commit ID示例:
- 6543f34:在失敗路徑添加free(seq)
- fa856b0:在通用錯誤路徑添加sk_GENERAL_NAME_free(ialt)
- b2474b2:錯誤路徑中調用SSL_SESSION_free(sess)
- 實現方式:代碼重構,增加統一清理標簽
示例代碼 DIFF:
@@ -648,8 +648,12 @@ static int ossl_ess_add1_signing_cert(PKCS7_SIGNER_INFO *si,}OPENSSL_free(pp);
- return PKCS7_add_signed_attribute(si, NID_id_smime_aa_signingCertificate,
- V_ASN1_SEQUENCE, seq);
+ if (!PKCS7_add_signed_attribute(si, NID_id_smime_aa_signingCertificate,
+ V_ASN1_SEQUENCE, seq)) {
+ ASN1_STRING_free(seq);
+ return 0;
+ }
+ return 1;}
2. 智能指針與引用計數
- 措施:使用組件特定釋放函數(如SSL_SESSION_free)
- Commit ID示例:
- 4798e06:使用X509V3_EXT_free釋放擴展對象
- 70f589a:BN_rand_range提前檢查rnd指針NULL
- 20c7feb:DTLS記錄層釋放前檢查消息緩沖區
- 實現方式:封裝資源管理類,自動處理生命周期
示例代碼 DIFF:
@@ -136,6 +136,11 @@ static int bnrand_range(BNRAND_FLAG flag, BIGNUM *r, const BIGNUM *range,int n;int count = 100;+ if (r == NULL) {
+ ERR_raise(ERR_LIB_BN, ERR_R_PASSED_NULL_PARAMETER);
+ return 0;
+ }
+
3. 防御性編程
- 措施:分配前驗證參數,失敗時立即釋放已分配資源
- Commit ID示例:
- d48874a:檢查sk_OPENSSL_STRING_push返回值
- 19b87d2:s390x_HMAC_CTX_copy先釋放目標緩沖區
- 62b0a0d:CTLOG_new_from_base64限制填充字符數
- 實現方式:前置條件檢查,分層錯誤處理
4. 工具輔助修復
- 措施:集成靜態分析結果,針對性修復
- Commit ID示例:
- 33c4187:根據Coverity報告修復apps/cmp.c泄漏
- 8515534:根據Valgrind輸出修復BIO引用計數
- d0a4b7d:修復libFuzzer發現的DTLS碎片泄漏
- 實現方式:建立漏洞修復工作流,關聯工具報告
5 代碼提交分析
泄漏類型 | 部分Commit ID示例 | 發現方式 |
---|---|---|
動態內存未釋放 | 6543f34, 20a2f3b, d48874a, 70f589a | 人工審查、單元測試 |
資源句柄未關閉 | 857c223, 9561e2a, 35b1a43, 28adea9 | Valgrind、功能測試 |
容器/棧泄漏 | 1c42216, 55500ea, 4798e06, 74c929d | Coverity、集成測試 |
跨模塊資源泄漏 | 8c63b14, a076951, 3a1d2b5, 981a5b7 | 接口測試、人工審查 |
異常路徑泄漏 | 009fa4f, fa856b0, b2474b2, 026e012 | 錯誤注入、ASan |
靜態分析發現 | 009fa4f, 33c4187, 8515534, a1d6a0b | Coverity、Clang Scan-Build |
動態內存檢測 | a906436, 6b5c7ef, 9c7a780, d0a4b7d | ASan、Valgrind |
單元功能測試 | 21f0b80, 81d61a6, c5d0612, 875db35 | CI測試、覆蓋率分析 |
模糊測試發現 | 6afef8b, 1400f01, a1d6a0b, 1b8f193 | libFuzzer、oss-fuzz |
可以在命令行通過 git show <Commit ID>
查看特定提交的詳細信息。
6 總結
OpenSSL內存泄漏修復呈現以下特點:
- 泄漏成因:錯誤處理路徑遺漏釋放、跨模塊資源管理不一致、高并發場景競爭條件。
- 檢測手段:靜態分析( Coverity )、動態檢測( ASan )、模糊測試( libFuzzer )形成互補。
- 修復趨勢:從被動修復轉向主動防御,通過RAII原則和智能指針減少人為錯誤。
- 最佳實踐:統一錯誤處理模式、定期運行內存分析工具、在CI中集成泄漏檢測。
我的后續文章將具體講解這些典型漏洞的原理和檢測工具的具體應用。