
CVE-2020-8597 (pppd EAP) का सिंथेटिक CWE-120 स्टैक बफर ओवरफ्लो वेरिएंट, CodeQL स्टैटिक-विश्लेषण लक्ष्य के रूप में
एक जानबूझकर असुरक्षित, ~170-पंक्ति का C प्रोग्राम जिसे CodeQL स्टैटिक-एनालिसिस
टारगेट के रूप में उपयोग किया जाता है। यह CVE-2020-8597 के बग क्लास को पुनरुत्पादित करता है — pppd EAP
rhostname स्टैक बफ़र ओवरफ़्लो (CWE-120) — एक ऐसे प्रोग्राम में जो pppd के किसी भी फ़ंक्शन नाम, कॉल डेप्थ, या डिस्पैच संरचना को साझा नहीं करता।
लक्ष्य एक जनरैलिटी टेस्ट है: pppd बग को पकड़ने के लिए लिखी गई CodeQL क्वेरी को इस प्रोग्राम पर भी, बिना संपादन के, फ़ायर होना चाहिए। यदि ऐसा होता है, तो क्वेरी बग क्लास को व्यक्त करती है, न कि मूल कोड के आकार को।
यह प्रोग्राम जानबूझकर असुरक्षित है और केवल विश्लेषण के लिए मौजूद है। इसे डिप्लॉय न करें। जिस बग की यह नकल करता है वह सार्वजनिक है (CVE-2020-8597, 2020 में प्रकट)।
एक हमलावर-व्युत्पन्न लंबाई को एक निश्चित-आकार के बफ़र में कॉपी किया जाता है, बिना किसी गार्ड के जो उस लंबाई को बफ़र के आकार से संबंधित करता हो।
हर मामले में एक बाउंड्स चेक मौजूद है — यह बस उन दो मात्राओं को संबंधित करने में विफल रहता है जो मायने रखती हैं। इसे गलत करने के ठीक दो तरीके हैं, और प्रोग्राम में प्रत्येक में से एक है:
sizeof(dest) के बजाय प्राप्त फ़्रेम के विरुद्ध। ओवर-रीड को रोकता है, ओवर-राइट के बारे में कुछ नहीं करता। (handle_hello)sizeof(dest) का नाम लेता है — यह बिल्कुल बफ़र बाउंड जैसा दिखता है — लेकिन कॉपी लंबाई के रूप में उपयोग किए गए वेरिएबल से भिन्न वेरिएबल को सीमित करता है। (handle_stat)दूसरा कठिन है, और यही pppd के मृत
vallen >= len + sizeof(rhostname) चेक का सार है: एक तुलना जो गंतव्य आकार का उल्लेख करती है जबकि ऐसी चीज़ को सीमित करती है जो कॉपी लंबाई नहीं है। एक क्वेरी जो केवल पूछती है "क्या यहाँ कोई तुलना sizeof(dest) का उल्लेख करती है?" उसके द्वारा चुप करा दी जाती है।
| pppd / CVE-2020-8597 | यह प्रोजेक्ट | |
|---|---|---|
| स्रोत | PPP fd पर read() | UDP सॉकेट पर recvfrom() |
| डिस्पैच | ग्लोबल struct protent *protocols[], प्रोटोकॉल नंबर पर रैखिक मिलान | फ़ाइल-लोकल const struct frame_op ops[], 1-बाइट टैग पर रैखिक मिलान |
| सिंक तक गहराई | get_input → (*input) → eap_input → eap_request | dispatch_frame → (*handle) → handle_hello |
| गंतव्य | char rhostname[256] | char name[64] |
| गलत गार्ड | vallen पैकेट len द्वारा सीमित | vlen फ़्रेम plen द्वारा सीमित |
दोनों उस एक गुण को बनाए रखते हैं जो इसे डेटा-फ़्लो बनाता है, grep नहीं: स्रोत और सिंक के बीच एक फ़ंक्शन-पॉइंटर टेबल के माध्यम से अप्रत्यक्ष कॉल।
| हैंडलर | पंक्ति | चेक मौजूद | निर्णय |
|---|---|---|---|
handle_hello() | सिंक :80 पर | vlen > plen - 2 — सही मान, गलत बाउंड | फ़ायर होना चाहिए |
handle_echo() | कॉपी :106 पर | vlen >= sizeof(buf) — दोनों सही | चुप रहना चाहिए — नेगेटिव कंट्रोल |
handle_stat() | सिंक :145 पर | hlen >= sizeof(report) — सही बाउंड, गलत मान | फ़ायर होना चाहिए |
handle_stat विभेदक मामला है। इसका चेक sizeof(report) का नाम लेता है, इसलिए एक क्वेरी जो गंतव्य आकार का उल्लेख करने वाली किसी भी तुलना को स्वीकार करती है, इसे गार्डेड मानती है और बग को चूक जाती है। इसे पकड़ने के लिए जाँचे जा रहे मान की तुलना कॉपी लंबाई के रूप में उपयोग किए गए मान से करना आवश्यक है — ग्लोबल वैल्यू नंबरिंग। इसे क्वेरी से हटा दें और यह हैंडलर एक फ़ॉल्स नेगेटिव बन जाता है जबकि अन्य सभी साइटें अपना निर्णय बनाए रखती हैं।
एक UDP डेटाग्राम = एक फ़्रेम:
[ type : 1 ] [ length : 2, big-endian ] [ value : length bytes ]
type 0x01 → hello, 0x02 → echo, 0x03 → stat। 65 और ~2045 के बीच घोषित लंबाई वाला एक hello फ़्रेम name[64] को ओवरफ़्लो करता है। एक stat फ़्रेम इसके बजाय दो एक-बाइट लंबाइयाँ ले जाता है — एक हेडर लंबाई और एक बॉडी लंबाई — और 32 से ऊपर कोई भी बॉडी लंबाई report[32] को ओवरफ़्लो करती है, चाहे हेडर लंबाई कुछ भी कहे।
make # gcc -Wall -Wextra -O0 -g -o tlv_server tlv_server.c
Linux/POSIX (BSD सॉकेट)। बिना किसी चेतावनी के साफ़ बिल्ड होता है।
CodeQL एक वास्तविक कंपाइल को ट्रेस करता है, इसलिए साफ़ से बिल्ड करें:
make clean
codeql database create db --language=cpp --command="make"
# or, without the clean step:
codeql database create db --language=cpp --command="make -B"
फिर db के विरुद्ध Part 3 क्वेरी चलाएँ; इसे handle_hello में memcpy और handle_stat में एक की रिपोर्ट करनी चाहिए, और handle_echo पर चुप रहना चाहिए।
क्वेरी और उसके रन निर्देश codeql/ में हैं।
जब भी कोई हैंडलर जोड़ा जाता है स्रोत बदल जाता है, इसलिए डेटाबेस को पुनर्निर्मित करें —
CodeQL database create के समय कोड का स्नैपशॉट लेता है और एक मौजूदा db/ नए कोड को नहीं देखेगा।