प्रतिनिधि इंटरव्यू विषय

आप अविश्वसनीय (untrusted) Apache Arrow IPC डेटा को सुरक्षित रूप से कैसे इनजेस्ट करते हैं?

डेटाकठिन
Offer.cc संपादकीय टीमप्रकाशित अपडेट किया गया

प्रश्न

आपकी एनालिटिक्स सेवा पार्टनर्स से Arrow IPC के माध्यम से बैच डेटा प्राप्त करती है। इसे कॉलमर परफॉरमेंस बनाए रखनी चाहिए, लेकिन इनपुट दुर्भावनापूर्ण रूप से लंबाई (lengths), ऑफ़सेट (offsets), डिक्शनरी या नेस्टेड प्रकारों का निर्माण कर सकते हैं। वैलिडेशन, संसाधन नियंत्रण (resource governance), आइसोलेशन, त्रुटि प्रबंधन (error handling) और ऑब्जर्वेबिलिटी डिज़ाइन करें, और बताएं कि आपको ज़ीरो-कॉपी कब छोड़नी चाहिए।

1. परिदृश्य और थ्रेट मॉडल (Scenario and threat model)

एक एनालिटिक्स सेवा बाहरी टेनेंट्स से Arrow IPC स्ट्रीम प्राप्त करती है और उन्हें Python, Rust और Java उपभोक्ताओं को पास करती है। एक हमलावर असंगत स्कीमा, अत्यधिक लंबाई, सीमा से बाहर (out-of-bounds) ऑफ़सेट, रिकर्सिव नेस्टिंग, दुर्भावनापूर्ण डिक्शनरी या ऐसे बफ़र्स तैयार कर सकता है जो रिसीवर के बजट से अधिक हों। लक्ष्य बैच थ्रूपुट बनाए रखना है, साथ ही यह सुनिश्चित करना है कि पार्स विफलता केवल वर्तमान अनुरोध को प्रभावित करे।

सबसे पहले ट्रस्ट बाउंड्री परिभाषित करें: नेटवर्क बाइट्स, IPC मेटाडेटा, बफ़र सामग्री और व्यावसायिक स्कीमा अविश्वसनीय हैं; एक प्रमाणित टेनेंट का अर्थ स्वचालित रूप से सुरक्षित डेटा नहीं है। सुरक्षा मॉडल को केवल एक भाषा बाइंडिंग के डिफ़ॉल्ट पर निर्भर रहने के बजाय Arrow Columnar Format, C Data Interface और IPC को कवर करना चाहिए।

2. Arrow की सुरक्षा धारणाएँ स्पष्ट करें

कॉलमर फ़ॉर्मेट बफ़र्स, लंबाई, ऑफ़सेट, नल बिटमैप्स, डिक्शनरी और नेस्टेड लेआउट का वर्णन करता है। ज़ीरो-कॉपी डेटा कॉपी करने को कम करती है लेकिन यह किसी उपभोक्ता को बाहरी इनपुट द्वारा वर्णित मेमोरी क्षेत्रों को पढ़ने की अनुमति दे सकती है। एक सुरक्षित कार्यान्वयन डेटा को कंप्यूट इंजन को सौंपने से पहले वास्तविक बफ़र्स के विरुद्ध मेटाडेटा को मान्य (validate) करता है।

एक वैध फ़ॉर्मेट विनिर्देश को मान्य इनपुट के बराबर न समझें। प्रोटोकॉल संस्करणों, एक्सटेंशन प्रकारों और भाषा कार्यान्वयनों की अलग-अलग सीमाएँ होती हैं। समर्थित संस्करणों और प्रकारों के सेट को निश्चित करें, और अज्ञात फ़ील्ड्स के लिए एक स्पष्ट नीति परिभाषित करें।

3. स्कीमा, लंबाई और ऑफ़सेट को मान्य करें

पहला चरण केवल सीमित मेटाडेटा को पार्स करता है और फ़ील्ड गणना, नेस्टिंग गहराई, डेटा प्रकार, डिक्शनरी संदर्भ और बैच पंक्ति सीमाओं की जांच करता है। प्रत्येक बफ़र के लिए, सत्यापित करें कि offset + length ओवरफ़्लो नहीं हो सकता है और वास्तविक आवंटन के भीतर है। परिवर्तनीय लंबाई वाले स्ट्रिंग्स और सूचियों के लिए, मोनोटोनिक ऑफ़सेट सत्यापित करें जो मान बफ़र (values buffer) के अंदर रहें।

text
if offset < 0 or length < 0: reject
if offset > buffer_size: reject
if length > buffer_size - offset: reject
if nesting_depth > MAX_DEPTH: reject

ओवरफ़्लो-सुरक्षित अंकगणित का उपयोग करें, नकारात्मक या अत्यधिक बड़े पूर्णांकों और स्कीमा-असंगत नल गणनाओं को अस्वीकार करें। वैलिडेशन से पहले हमलावर द्वारा घोषित लंबाई को कभी भी आवंटित न करें।

4. संसाधन बजट और बैकप्रेशर लागू करें

मेटाडेटा बाइट्स, कॉलम, पंक्तियों, कुल बफ़र बाइट्स, नेस्टिंग गहराई, डिक्शनरी आकार और CPU समय के लिए प्रति-अनुरोध बजट सेट करें। प्रत्येक बैच पार्सर को बजट पास करें; इसके पार होने पर अनुरोध को रद्द करें और आवंटन को तुरंत मुक्त करें।

एक बार में मेमोरी में संपूर्ण अपलोड लोड करने के बजाय IPC स्ट्रीम के लिए सीमित रीड और बैच बैकप्रेशर का उपयोग करें। कंप्रेशन, डिक्शनरी और दोहराए गए संदर्भों से होने वाले विस्तार को सीमित करें ताकि एक छोटा इनपुट बड़ा आवंटन न बन सके। प्रति टेनेंट कोटा को अलग (isolate) करें ताकि एक दुर्भावनापूर्ण अनुरोध प्रत्येक वर्कर का उपभोग न कर सके।

5. तय करें कि ज़ीरो-कॉपी कब रखनी है और कब कॉपी करना है

केवल बफ़र स्वामित्व, जीवनकाल और सीमाओं को साबित करने और यह पुष्टि करने के बाद ही केवल-पढ़ने योग्य (read-only) ज़ीरो-कॉपी रखें कि डाउनस्ट्रीम कोड इसे म्यूटेट नहीं कर सकता है। नेटवर्क प्राप्त बफ़र्स, अस्थायी mmaps, या क्रॉस-लैंग्वेज FFI मेमोरी जिसे रिलीज़, पुन: उपयोग या लिखा जा सकता है, उन्हें एक नियंत्रित अखाड़े (controlled arena) में कॉपी किया जाना चाहिए।

कॉपी करना कोई विफलता नहीं है; यह वह सुरक्षा सीमा है जो एक अविश्वसनीय जीवनकाल को नियंत्रित जीवनकाल में बदल देती है। प्रति कॉलम चुनें: मान्य संख्यात्मक बफ़र्स साझा करें, और बजट की अनुमति होने पर स्ट्रिंग्स, डिक्शनरी या नेस्टेड कॉलम को कॉपी करें। "पूर्ण ज़ीरो-कॉपी" के पीछे जोखिम छिपाने के बजाय कॉपी किए गए बाइट्स और लेटेंसी को रिकॉर्ड करें।

6. पार्सिंग, त्रुटियों और अनुकूलता को अलग करें

CPU, मेमोरी, फ़ाइल-डिस्क्रिप्टर और वॉल-टाइम सीमाओं वाले एक बाधित वर्कर या प्रोसेस में पार्सर चलाएं। संरचित कारण लौटाएं जैसे कि असमर्थित संस्करण, स्कीमा बेमेल, ऑफ़सेट सीमा से बाहर, या बजट पार होना; कभी भी कच्चे पेलोड या आंतरिक पते प्रतिध्वनित (echo) न करें।

एक्सटेंशन प्रकारों, अज्ञात मेटाडेटा और भविष्य के फ़ील्ड्स के लिए अनुमति सूचियों (allowlists) का उपयोग करें। अनुकूलता के लिए, क्वेरी इंजन में प्रवेश करने से पहले एक आंतरिक स्कीमा में कनवर्ट करें। Arrow 25 या भाषा बाइंडिंग को अपग्रेड करने से पहले, डिफ़रेंशियल परीक्षणों के रूप में उसी दुर्भावनापूर्ण कॉर्पस को चलाएं और त्रुटि श्रेणियों, पीक मेमोरी और परिणामों की तुलना करें।

7. निरीक्षण करें, फ़ज़िंग करें और घटनाओं पर प्रतिक्रिया दें

टेनेंट, फ़ॉर्मेट संस्करण, स्कीमा हैश, अस्वीकृति का कारण, इनपुट आकार, पार्स अवधि, पीक मेमोरी, कॉपी किए गए बाइट्स और रद्दीकरण रिकॉर्ड करें; संवेदनशील सामग्री को लॉग करने के बजाय पेलोड को हैश करें। प्रत्येक अस्वीकृति श्रेणी पर अलर्ट करें और एक गलत कॉन्फ़िगर किए गए क्लाइंट को एक निरंतर हमले से अलग पहचानें।

क्रैश और सीमा से बाहर की पहुंच का पता लगाने के लिए AddressSanitizer, MemorySanitizer, या किसी समकक्ष टूल का उपयोग करके यादृच्छिक स्कीमा, ऑफ़सेट, नल बिटमैप्स, डिक्शनरी और कटे हुए (truncated) स्ट्रीम को फ़ज़ करें। न्यूनतम पुनरुत्पादक (reproducers) रखें और सुधार के बाद उन्हें दोबारा जांचें (regress करें)। उत्पादन विसंगति के लिए, पहले टेनेंट या फ़ॉर्मेट संस्करण को अलग करें, फिर नमूने का विश्लेषण करें और प्रभावित क्रेडेंशियल्स को रोटेट करें।

8. रूब्रिक और अनुवर्ती प्रश्न

समझाना आवश्यक है

  • Arrow मेटाडेटा, बफ़र्स और व्यावसायिक स्कीमा को अविश्वसनीय मानें; लंबाई, ऑफ़सेट, गहराई और डिक्शनरी संदर्भों को मान्य करें।
  • ज़ीरो-कॉपी को एक बिना शर्त लक्ष्य मानने के बजाय बजट, बैकप्रेशर और आइसोलेशन के साथ मेमोरी और CPU को नियंत्रित करें।
  • कॉपी सीमाएं, संरचित त्रुटियां, फ़ज़िंग, ऑब्जर्वेबिलिटी और अपग्रेड रणनीति प्रदान करें।

अनुवर्ती प्रश्न (Follow-up questions)

  • एक स्ट्रिंग कॉलम में मोनोटोनिक ऑफ़सेट हैं, लेकिन अंतिम मान बफ़र से अधिक है। आप इसे किस स्तर पर अस्वीकार करते हैं?
  • आप किसी डिक्शनरी या नेस्टेड सूची को असीमित संसाधन लागत में विस्तारित होने से कैसे रोकते हैं?
  • आप यह कैसे साबित करेंगे कि Python, Rust और Java बाइंडिंग एक दुर्भावनापूर्ण इनपुट के लिए समान निष्कर्ष पर पहुँचते हैं?

स्कोरिंग गाइड

एक उत्कृष्ट उत्तर फ़ॉर्मेट वैलिडेशन, संसाधन नियंत्रण, मेमोरी स्वामित्व और रनटाइम साक्ष्य को जोड़ता है: असंभव लेआउट को अस्वीकार करना, बजट के भीतर बैचों का उपभोग करना, और आइसोलेशन, फ़ज़िंग और मेट्रिक्स के साथ यह साबित करना कि अविश्वसनीय इनपुट सेवा-व्यापी विफलता नहीं बन सकता है।

सार्वजनिक स्रोत

संबंधित प्रश्न