
इतना समय पहले, OpenSSL में एक भेद्यता का पता चला था कि केवल आलसी के बारे में बात नहीं की जा सकती थी। मुझे पता है कि पीवीएस-स्टूडियो उस त्रुटि को खोजने में सक्षम नहीं है जो इस भेद्यता की ओर जाता है। इसलिए, मैंने फैसला किया कि ओपनएसएसएल के बारे में कोई भी लेख लिखने का कोई कारण नहीं था। हाल के दिनों में, इस विषय पर बहुत सारे लेख आए हैं। हालाँकि, मुझे पत्रों की एक झड़ी लगी, जिसमें मुझे यह बताने के लिए कहा गया कि क्या पीवीएस-स्टूडियो को यह त्रुटि मिल सकती है। मैंने छोड़ दिया और यह लेख लिखा।
ओपनएसएसएल को मान्य करें
मुझे लगता है कि हर कोई पहले से ही जानता है कि
ओपनएसएसएल में एक गंभीर भेद्यता की खोज की गई है। यदि, फिर भी, किसी ने इसे याद किया या अधिक विवरण जानना चाहता है, तो मैं इस विषय पर कई लेखों से परिचित होने का प्रस्ताव करता हूं:
संक्षिप्त होने के लिए, अन्य लोगों के डेटा तक पहुंच की भेद्यता लगभग 2 वर्षों से कोड में मौजूद है। इस समय के दौरान, एक भी कोड विश्लेषक ने इसे नहीं पाया, हालांकि केवल आलसी व्यक्ति ने इस पुस्तकालय का परीक्षण नहीं किया था।
हमने ओपनएसएसएल का भी परीक्षण किया। यहां इस विषय पर एक नोट दिया गया है: "
ओपनएसएसएल के बारे में थोड़ा ।" कुछ हमने पाया, लेकिन यह कुछ भी गंभीर नहीं है। अब ये त्रुटियां ठीक हो गई हैं। तो व्यर्थ की जाँच नहीं।
मैंने यह निर्दिष्ट नहीं किया कि हमने ओपनएसएसएल की जांच की है जब वहां पहले से ही हार्टलेस बग था या नहीं। किसी भी मामले में, मुझे पता है कि पीवीएस-स्टूडियो इस तरह के बग का पता लगाने में सक्षम नहीं है। आमतौर पर इसका पता लगाना मुश्किल होता है। ओपनएसएसएल परियोजना को विभिन्न उपकरणों के साथ जांचा और परखा गया था, और उनमें से एक को भी यह त्रुटि नहीं मिली। उदाहरण के लिए, मुझे एक गलती नहीं मिली, आवरण स्कैन कोड विश्लेषक के बीच नेता। इस पर नोट्स: "
हार्दिक और स्थैतिक विश्लेषण ", "
हार्दिक और स्थिर विश्लेषण (2) "।
तथ्य यह है कि इस तरह की त्रुटि स्थैतिक विश्लेषण का उपयोग करना बहुत मुश्किल है। कोड बहुत भ्रामक है। आपको स्मृति में संग्रहीत मानों पर विचार करने की आवश्यकता है, आपको यह समझने की आवश्यकता है कि स्पष्ट प्रकार के रूपांतरणों के पीछे क्या छिपा हुआ है और इसी तरह। यहां तक कि एक व्यक्ति को यह समझना मुश्किल है कि समस्या क्या है। स्थैतिक विश्लेषक यहां से गुजरते हैं। यह स्थैतिक विश्लेषण पद्धति में एक दोष नहीं है। यह सिर्फ इतना है कि गलती वास्तव में जटिल है। शायद, ऐसा कोई उपकरण नहीं है जो इस तरह के दोष का पता लगा सकता है यदि आप ऐसी संरचनाओं की खोज के लिए इसे पूर्व-प्रशिक्षित नहीं करते हैं।
यह ध्यान दिया जाना चाहिए कि अभी भी ज्ञात और अज्ञात स्थिर विश्लेषण उपकरण हैं जिन्हें सॉफ्टवेयर बुकमार्क्स की खोज के लिए डिज़ाइन किया गया है। शायद ऐसे उपकरण एक भेद्यता पा सकते थे, लेकिन मुझे इस पर संदेह है। यदि वे पाते हैं, तो वे इसे अपने विश्लेषक के लिए एक विज्ञापन के रूप में उपयोग करेंगे। बेशक, विकल्प है कि विशेष सेवाओं के भीतर विकसित किए गए कुछ उपकरण इस भेद्यता को ढूंढते हैं, लेकिन वे इसके बारे में विशेष रूप से बात नहीं करते हैं। हालांकि, साजिश थीसिस यहाँ से शुरू होती है, तो चलो उस बारे में बात नहीं करते हैं।
मेरी निजी राय यह सिर्फ एक गलती है, बुकमार्क नहीं। स्थैतिक विश्लेषण उपकरण यह नहीं जानते कि इसका पता कैसे लगाया जाए, क्योंकि यह जटिल है। वह सब है।
यह लेख को समाप्त कर सकता है, लेकिन यह होगा, यह बिल्कुल भी दिलचस्प नहीं होगा। इसलिए मैंने
पीवीएस-स्टूडियो के साथ ओपनएसएसएल को फिर से जांचा। मुझे कुछ विशेष नहीं मिला, लेकिन फिर भी, आइए कोड के कुछ अनुभागों को देखें।
इतना कम क्यों है? हां, क्योंकि ओपनएसएसएल एक गुणवत्ता परियोजना है। तथ्य यह है कि इसमें एक गंभीर भेद्यता पाई जाती है, इसका मतलब यह नहीं है कि कोड भयानक है। मुझे लगता है कि कई अनुप्रयोगों में बहुत बड़े छेद हैं, बस किसी को उनकी आवश्यकता नहीं है। साथ ही, ओपनएसएसएल परियोजना का परीक्षण कई उपकरणों द्वारा किया जाता है।
विश्लेषण के परिणाम
एक बार फिर मैं दोहराना चाहता हूं कि मुझे कोई विशेष त्रुटि नहीं मिली। बेहतर होगा कि हम नीचे दिए गए पाठ को त्रुटियों के विवरण के रूप में नहीं, बल्कि उस कोड पर टिप्पणी के रूप में मानें जो मुझे गलत लग रहा था। मैं बाद में टिप्पणियां नहीं देखना चाहता कि मैं एक हाथी को एक मक्खी से भड़का रहा हूं।
संदेहास्पद तुलना
typedef struct ok_struct { .... size_t buf_len_save; size_t buf_off_save; .... } BIO_OK_CTX; static int ok_read(BIO *b, char *out, int outl) { .... BIO_OK_CTX *ctx; .... if(ctx->buf_len_save - ctx->buf_off_save > 0) .... }
PVS-Studio चेतावनी: V555 'A - B> 0' तरह की अभिव्यक्ति 'A! = B' के रूप में काम करेगी। बायो_ओक 243
अभिव्यक्ति (ctx-> buf_len_save - ctx-> buf_off_save> 0) काम नहीं करती क्योंकि यह पहली नज़र में लग सकता है।
ऐसा लगता है कि यहां वे स्थिति (ctx-> buf_len_save> ctx-> buf_off_save) की जांच करना चाहते हैं। ऐसा नहीं है। तथ्य यह है कि तुलना किए गए चर अहस्ताक्षरित प्रकार के हैं। एक अहस्ताक्षरित चर को दूसरे अहस्ताक्षरित चर से घटाकर एक अहस्ताक्षरित प्रकार का परिणाम देता है।
स्थिति (ctx-> buf_len_save - ctx-> buf_off_save> 0) हमेशा संतुष्ट रहती है यदि चर मेल नहीं खाते हैं। दूसरे शब्दों में, निम्नलिखित दो भाव समतुल्य हैं:
- (ctx-> buf_len_save - ctx-> buf_off_save> 0)
- (ctx-> buf_len_save! = ctx-> buf_off_save)
उन लोगों के लिए स्पष्टीकरण जो सी भाषा से बहुत परिचित नहीं हैं। अनुभवी डेवलपर्स पढ़ नहीं सकते हैं।
मान लें कि हमारे पास दो 32-बिट अहस्ताक्षरित चर हैं:
अहस्ताक्षरित ए = 10;
अहस्ताक्षरित B = 20;
जाँच करें कि क्या स्थिति (ए - बी> 0) संतुष्ट है।
घटाव (ए - बी) का परिणाम 10u - 20u = 0xFFFFFFF6u = 4294967287u है।
अगला, हम शून्य के साथ अहस्ताक्षरित संख्या 4294967286u की तुलना करते हैं। शून्य को भी एक अहस्ताक्षरित प्रकार के लिए रखा गया है, लेकिन इससे कोई फर्क नहीं पड़ता।
अभिव्यक्ति (4294967286u> 0u) सत्य है।
अभिव्यक्ति (ए - बी> 0) केवल एक मामले में झूठी होगी, जब ए == बी।
क्या यह तुलना एक गलती है? मुझे नहीं पता, क्योंकि मैं प्रोजेक्ट डिवाइस से परिचित नहीं हूं। मुझे लगता है कि कोई गलती नहीं है।
सबसे अधिक संभावना है, यह मामला है। चर 'buf_len_save' आमतौर पर चर 'buf_off_save' से बड़ा होता है। केवल कभी-कभी परिवर्तनशील 'buf_off_save' का मान 'buf_len_save' में संग्रहीत मान तक पहुँच जाता है। इस मामले में, जब चर समान होते हैं और इस चेक की जरूरत होती है। मामला जब (buf_len_save <buf_off_save) शायद असंभव है।
एक uninitialized चर जो परेशानी का कारण नहीं है
एक जगह है जहां एक असिंचित चर का उपयोग किया जा सकता है। लेकिन यह किसी भी बुरे परिणाम की ओर नहीं ले जाएगा। यहाँ यह कोड है:
int PEM_do_header(....) { int i,j,o,klen; .... if (o) o = EVP_DecryptUpdate(&ctx,data,&i,data,j); if (o) o = EVP_DecryptFinal_ex(&ctx,&(data[i]),&j); .... j+=i; if (!o) { PEMerr(PEM_F_PEM_DO_HEADER,PEM_R_BAD_DECRYPT); return(0); } .... }
PVS-Studio चेतावनी: V614 संभावित रूप से असंगठित चर 'i' का उपयोग किया जाता है। pem_lib.c 480
चर (i) यदि (ओ == असत्य) है, तो असंगठित हो सकता है। नतीजतन, यह स्पष्ट नहीं है कि 'जे' में क्या जोड़ा जाएगा। यह डरावना नहीं है, क्योंकि यदि (ओ == गलत), त्रुटि हैंडलर आग लगाता है और फ़ंक्शन अपना काम बंद कर देता है।
कोड सही है, लेकिन सटीक नहीं है। चर 'ओ' को पहले जांचना बेहतर है, और उसके बाद ही 'आई' का उपयोग करें:
if (!o) { PEMerr(PEM_F_PEM_DO_HEADER,PEM_R_BAD_DECRYPT); return(0); } j+=i;
अजीब असाइनमेंट
#define SSL_TLSEXT_ERR_ALERT_FATAL 2 int ssl3_accept(SSL *s) { .... if (ret != SSL_ERROR_NONE) { ssl3_send_alert(s,SSL3_AL_FATAL,al); if (al != TLS1_AD_UNKNOWN_PSK_IDENTITY) SSLerr(SSL_F_SSL3_ACCEPT,SSL_R_CLIENTHELLO_TLSEXT); ret = SSL_TLSEXT_ERR_ALERT_FATAL; ret= -1; goto end; } .... }
पीवीएस-स्टूडियो चेतावनी: V519 'रेट' वेरिएबल को क्रमिक रूप से दो बार मान दिया गया है। शायद यह एक गलती है। चेक लाइनें: 376, 377. s3_srvr.c 377
शुरुआत में, चर 'रिट' को मान 2 और फिर -1 दिया जाता है। संभवतः, पहला असाइनमेंट बहुत कम है और दुर्घटना से कोड में बना हुआ है।
एक और मामला:
int dtls1_retransmit_message(....) { .... saved_state.enc_write_ctx = s->enc_write_ctx; saved_state.write_hash = s->write_hash; saved_state.compress = s->compress; saved_state.session = s->session; saved_state.epoch = s->d1->w_epoch; saved_state.epoch = s->d1->w_epoch; .... }
PVS-Studio चेतावनी: V519 'save_state.epoch' चर को क्रमिक रूप से दो बार मान दिया गया है। शायद यह एक गलती है। चेक लाइनें: 1277, 1278. d1_both.c 1278
संभावित अशक्त सूचक
कार्यक्रमों में सबसे आम गड़बड़ी (
मेरे अनुभव में ) एक सूचक को जांचने से पहले उसे रोकना है। यह हमेशा एक गलती नहीं है। अक्सर एक पॉइंटर कभी भी अशक्त नहीं होगा। हालांकि, यह संभावित खतरनाक कोड है। खासकर जब परियोजना तेजी से बदलती है।
ओपनएसएसएल में ऐसी गलतियाँ हैं:
int SSL_shutdown(SSL *s) { if (s->handshake_func == 0) { SSLerr(SSL_F_SSL_SHUTDOWN, SSL_R_UNINITIALIZED); return -1; } if ((s != NULL) && !SSL_in_init(s)) return(s->method->ssl_shutdown(s)); else return(1); } .... }
PVS-Studio चेतावनी: V595 के 'n' सूचक को उपयोग करने से पहले इसे nullptr के खिलाफ सत्यापित किया गया था। चेक लाइनें: 1013, 1019. ssl_lib.c 1013
शुरुआत में, सूचक 's' का उपयोग किया जाता है: (s-> हैंडशेक_फंक == 0)।
और उसके बाद ही इसकी जाँच की जाती है: (s! = NULL)।
एक और अधिक जटिल मामला:
#define bn_wexpand(a,words) \ (((words) <= (a)->dmax)?(a):bn_expand2((a),(words))) static int ubsec_dh_generate_key(DH *dh) { .... if(bn_wexpand(pub_key, dh->p->top) == NULL) goto err; if(pub_key == NULL) goto err; .... }
PVS-Studio चेतावनी: V595 'pub_key' पॉइंटर को nullptr के खिलाफ सत्यापित होने से पहले उपयोग किया गया था। चेक लाइनें: 951, 952. e_ubsec.c 951
यह समझने के लिए कि त्रुटि कहाँ है, आपको मैक्रोज़ का विस्तार करने की आवश्यकता है। फिर हमें निम्नलिखित कोड मिलते हैं:
if((((dh->p->top) <= (pub_key)->dmax)? (pub_key):bn_expand2((pub_key), (dh->p->top))) == ((void *)0)) goto err; if(pub_key == ((void *)0)) goto err;
सूचक 'pub_key' पर ध्यान दें।
सबसे पहले यह dereferenced है: (pub_key) -> dmax।
नीचे यह शून्य के लिए समानता के लिए जाँच की जाती है: (pub_key == ((शून्य *) 0))।
अतिरिक्त जांच
कोड के कई टुकड़े हैं जहां एक चर की एक ही मूल्य के साथ दो बार तुलना की जाती है। मुझे लगता है कि यह कोई गलती नहीं है। ऐसा लगता है कि सिर्फ दूसरा चेक दुर्घटना और सिर्फ सुपरफ्लस द्वारा लिखा गया है। इसे हटाया जा सकता है।
अतिरिक्त जाँच N १ int ASN1_PRINTABLE_type(const unsigned char *s, int len) { .... if (!( ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || (c == ' ') || <<<<==== ((c >= '0') && (c <= '9')) || (c == ' ') || (c == '\'') || <<<<==== (c == '(') || (c == ')') || (c == '+') || (c == ',') || (c == '-') || (c == '.') || (c == '/') || (c == ':') || (c == '=') || (c == '?'))) ia5=1; .... }
PVS-Studio चेतावनी: V501 समान उप-अभिव्यक्तियाँ हैं (c == '') 'बाईं ओर' और 'के दाईं ओर' || ऑपरेटर। a_print.c 76
मैंने "<<<< ====" का उपयोग करते हुए समान चेक पर प्रकाश डाला। मैंने पिछले लेख में इस डुप्लिकेट चेक के बारे में
लिखा था , लेकिन इसे ठीक नहीं किया गया है। तो यह निश्चित रूप से एक समस्या नहीं है।
अतिरिक्त जांच एन 2, एन 3 int ssl3_read_bytes(SSL *s, int type, unsigned char *buf, int len, int peek) { .... if ((type && (type != SSL3_RT_APPLICATION_DATA) && (type != SSL3_RT_HANDSHAKE) && type) || (peek && (type != SSL3_RT_APPLICATION_DATA))) .... }
PVS-Studio चेतावनी: V501 बाईं और 'ऑपरेटर' के दाईं ओर समान उप-अभिव्यक्तियाँ 'प्रकार' हैं। s3_pkt.c 952
यह दो बार जांचा जाता है कि चर 'प्रकार' का एक गैर-अक्षीय मान है।
माना कोड टुकड़ा दूसरी फ़ाइल में कॉपी किया गया है, इसलिए वहां एक अनावश्यक तुलना भी है: d1_pkt.c 760।
अमान्य पंक्ति लंबाई
जादू स्थिरांक का उपयोग करके तारों की लंबाई निर्धारित करना अच्छा नहीं है। गलती करना बहुत आसान है ओपनएसएसएल में, पीवीएस-स्टूडियो विश्लेषक ने ऐसे तीन स्थानों पर ध्यान दिया।
पहला असफल मैजिक नंबरयह प्रदर्शित करने के लिए कि यह एक गलती है, आइए BIO_write फ़ंक्शन को कॉल करने के कुछ उदाहरण देखें:
- BIO_write (bp, "एन्कोडिंग में त्रुटि \ n", 18)
- BIO_write (bp, "\ n", 1)
- BIO_write (बीपी, ":", 1)
- BIO_write (bp, ": BAD OBJECT", 11)
- BIO_write (बीपी, "बुरा बुलियन \ n", 12)
जैसा कि आप इन उदाहरणों से देख सकते हैं, अंतिम संख्या स्ट्रिंग की लंबाई निर्धारित करती है।
और अब, गलत कोड:
static int asn1_parse2(....) { .... if (BIO_write(bp,"BAD ENUMERATED",11) <= 0) goto end; .... }
PVS-Studio चेतावनी: V666 'BIO_write' फ़ंक्शन के तीसरे तर्क का निरीक्षण करने पर विचार करें। यह संभव है कि मान एक स्ट्रिंग की लंबाई के साथ मेल नहीं खाता है जो दूसरे तर्क के साथ पारित किया गया था। asn1_par.c 378
स्ट्रिंग "बीएड एनुमरेटेड" की लंबाई 11 नहीं है, बल्कि 14 अक्षर हैं।
दूसरा असफल मैजिक नंबर static int www_body(char *hostname, int s, unsigned char *context) { .... if ( ((www == 1) && (strncmp("GET ",buf,4) == 0)) || ((www == 2) && (strncmp("GET /stats ",buf,10) == 0))) .... }
PVS-Studio चेतावनी: V666 'strncmp' फ़ंक्शन के तीसरे तर्क का निरीक्षण करने पर विचार करें। यह संभव है कि मान एक स्ट्रिंग की लंबाई के साथ मेल नहीं खाता है जो पहले तर्क के साथ पारित किया गया था। s_server.c 2703
स्ट्रिंग "GET / आँकड़े" की लंबाई 10 नहीं है, बल्कि 11 वर्ण हैं। अंतिम अंतर को ध्यान में नहीं रखा गया है। एक छोटी सी दोष, लेकिन अभी भी एक दोष है।
तीसरा असफल मैजिक नंबर static int asn1_cb(const char *elem, int len, void *bitstr) { .... if (!strncmp(vstart, "ASCII", 5)) arg->format = ASN1_GEN_FORMAT_ASCII; else if (!strncmp(vstart, "UTF8", 4)) arg->format = ASN1_GEN_FORMAT_UTF8; else if (!strncmp(vstart, "HEX", 3)) arg->format = ASN1_GEN_FORMAT_HEX; else if (!strncmp(vstart, "BITLIST", 3)) arg->format = ASN1_GEN_FORMAT_BITLIST; else .... }
PVS-Studio चेतावनी: V666 'strncmp' फ़ंक्शन के तीसरे तर्क का निरीक्षण करने पर विचार करें। यह संभव है कि मान एक स्ट्रिंग की लंबाई के साथ मेल नहीं खाता है जो दूसरे तर्क के साथ पारित किया गया था। asn1_gen.c 371
मुसीबत यहाँ है:
if (!strncmp(vstart, "BITLIST", 3))
"BITLIST" स्ट्रिंग की लंबाई 7 वर्ण है।
थोड़ा विचलित हुआ। पाठक पूछ सकते हैं कि पीवीएस-स्टूडियो ऐसी त्रुटियों को कैसे ढूंढता है। मैं समझा दूंगा। यह फ़ंक्शन कॉल के बारे में जानकारी एकत्र करता है, इस मामले में strncmp (), और डेटा मैट्रिक्स बनाता है:
- vstart, "ASCII", 5
- vstart, "UTF8", 4
- vstart, "HEX", ३
- vstart, "BITLIST", 3
फ़ंक्शन में एक स्ट्रिंग तर्क और एक संख्यात्मक एक है। मूल रूप से, स्ट्रिंग की लंबाई संख्या के समान है। तो यह तर्क स्ट्रिंग की लंबाई को निर्दिष्ट करता है। यह एक जगह नहीं है और इसका मतलब है कि
V666 चेतावनी जारी की जानी चाहिए।
बहुत अच्छा नहीं है
"% 08lX" का उपयोग करके पॉइंटर मान प्रिंट करना अच्छा नहीं है। इसके लिए "% p" है।
typedef struct mem_st { void *addr; .... } MEM; static void print_leak_doall_arg(const MEM *m, MEM_LEAK *l) { .... BIO_snprintf(bufp, BUF_REMAIN, "number=%d, address=%08lX\n", m->num,(unsigned long)m->addr); .... }
फ़ंक्शन के लिए एक पॉइंटर नहीं दिया गया है, लेकिन प्रकार का मान (अहस्ताक्षरित लंबा)। इसलिए, संकलक और कुछ विश्लेषक चुप रहेंगे।
पीवीएस-स्टूडियो ने इस दोष को अप्रत्यक्ष तरीके से देखा। वह इस तथ्य को पसंद नहीं करता है कि सूचक स्पष्ट रूप से (अहस्ताक्षरित लंबे) प्रकार का है। यह गलत है। किसी ने गारंटी नहीं दी कि सूचक 'लंबी' प्रकार में फिट होगा। उदाहरण के लिए, यह Win64 में नहीं किया जा सकता है।
सही और छोटा कोड है:
BIO_snprintf(bufp, BUF_REMAIN, "number=%d, address=%p\n", m->num, m->addr);
ऐसे तीन स्थान हैं जहाँ पॉइंटर मान को सही तरीके से नहीं छापा गया है:
- mem_dbg.c 699
- bio_cb.c 78
- asn1_lib.c 467
निष्कर्ष
हालांकि
स्थैतिक विश्लेषणकर्ताओं को यह त्रुटि नहीं मिली, और यह बहुत लंबे समय तक चली, फिर भी मैं हर किसी से रोजमर्रा के काम में स्थैतिक विश्लेषण का उपयोग करने का आग्रह करता हूं। आपको बस एक चांदी की गोली देखने की ज़रूरत नहीं है जो सभी समस्याओं को हल करेगी और प्रोग्राम कोड को त्रुटियों से पूरी तरह से बचाएगा। सबसे अच्छा परिणाम एक एकीकृत दृष्टिकोण में प्राप्त किया जाता है: इकाई परीक्षण,
स्थिर और गतिशील विश्लेषण , प्रतिगमन परीक्षण, और इसी तरह। स्टेटिक विश्लेषण कोडिंग चरण में बहुत सारे टाइपो और बेवकूफ त्रुटियों को खत्म कर देगा और इससे अन्य उपयोगी चीजों पर समय की बचत होगी, जैसे कि नई कार्यक्षमता या अधिक गहन परीक्षण लिखना।
हमारे
पीवीएस-स्टूडियो कोड विश्लेषक का प्रयास करें।
यह लेख अंग्रेजी में है।
यदि आप इस लेख को अंग्रेजी बोलने वाले दर्शकों के साथ साझा करना चाहते हैं, तो कृपया अनुवाद के लिंक का उपयोग करें: एंड्री कारपोव।
OpenSSL प्रोजेक्ट की जाँच के बारे में एक बोरिंग लेख ।