15 पॉइंट द्वारा outsideris 2021-03-21 | 1 टिप्पणियां | WhatsApp पर शेयर करें

पिछले 8 मार्च को एक security vulnerability के कारण GitHub.com के सभी users को log out कर दिया गया था.

  • 2 मार्च को एक user ने report किया कि login करने पर वह किसी दूसरे user के रूप में authenticate हो गया. उस user ने तुरंत log out किया, लेकिन इस समस्या की report की और तुरंत जांच शुरू की गई. कुछ घंटों बाद एक दूसरे user ने भी मिलती-जुलती issue report की.

  • शुरुआती जांच में पता चला कि report किए गए समय पर एक user का session दो IPs पर साझा किया गया था.

  • हाल के infrastructure changes की जांच में पता चला कि हाल ही में load balancer और routing को upgrade किया गया था, और यहां HTTP keepalives में बदलाव किया गया था. यह संबंधित लग रहा था, लेकिन आगे की जांच में यह असंबंधित निकला.

  • फिर भी infrastructure की जांच के दौरान यह पता चला कि गलत session पाने वाले requests बिल्कुल उसी machine और process पर handle हुए थे.

  • logs देखने पर पता चला कि response body सामान्य थी और केवल cookie गलत भेजी गई थी, और उसी process में handle हुए किसी दूसरे user की cookie गलती से भेज दी गई थी. report किए गए मामलों में एक में दोनों requests लगातार आए थे, और दूसरे में उन दो requests के बीच दो और requests थे.

  • यहां से यह hypothesis बना कि उसी Ruby process में handle हुए requests के बीच state leak हुई थी, और यह कैसे संभव हुआ, यही सवाल बन गया.

  • हाल के changes की समीक्षा में पता चला कि performance सुधारने के लिए users के लिए enabled features को जांचने वाली logic को request handling के दौरान चलाने के बजाय, निश्चित अंतराल पर update होने वाले background thread में चलाने के लिए बदला गया था. जांच का फोकस इस thread-safe behavior पर गया.

  • GitHub.com की main application Ruby on Rails पर है, और इसमें कई components ऐसे हैं जो multi-threading में चलने के लिए लिखे ही नहीं गए थे.

  • application में threads पहले से इस्तेमाल हो रहे थे, लेकिन नए background thread ने exception handling routine में अनपेक्षित behavior पैदा कर दिया.

  • जब background thread में exception होती थी, तो error log में background thread और चल रहे request, दोनों की जानकारी शामिल होती थी.

  • शुरुआत में लगा कि असंबंधित request का data background thread से log होना सिर्फ internal reporting issue के कारण हुई mismatch है.

  • Rails हर request के लिए नया controller object बनाकर process करता है, इसलिए इसे सुरक्षित माना गया.

  • इसलिए यह अब भी स्पष्ट नहीं था कि यह समस्या क्यों हो रही थी.

  • Rails application में Rack HTTP server के रूप में इस्तेमाल हो रहे Unicorn के बारे में पता चला कि वह हर request के लिए नया अलग env object नहीं बनाता, और यहीं से breakthrough मिला.

  • इसके बजाय Unicorn हर request के लिए एक Ruby hash allocate करता है और फिर उसे clear कर देता है.

  • इससे समझ आया कि background thread के logs reporting mismatch नहीं थे, बल्कि request data वास्तव में share हो रहा था.

  • इसके बाद इस race condition को development environment में reproduce करने की कोशिश की गई, और पता चला कि इस स्थिति के बनने के लिए शुरुआत anonymous request से होनी चाहिए.

  1. एक anonymous request (request #1) आती है, तो exception reporting library में एक callback register होता है. इस callback में उस Rails controller object का reference होता है जो Unicorn द्वारा दिए गए Rack request environment object को access करता है.

  2. background process में exception होती है, तो report करने के लिए पूरा context copy किया जाता है, और इसमें callback भी शामिल होता है.

  3. main thread में एक नया logged-in request शुरू होता है. (request #2)

  4. background thread में exception reporting context callback को process करती है. वह user session identifier पढ़ने की कोशिश करती है, लेकिन वह नहीं मिलता, इसलिए request #1 के Rails controller के जरिए authentication system को request भेजती है. क्योंकि Rack हर request में वही object इस्तेमाल करता है, controller को request #2 की session cookie मिल जाती है.

  5. main thread में request #2 समाप्त हो जाता है.

  6. एक और logged-in request आती है. (request #3) authentication पहले ही पूरी हो चुकी है.

  7. background thread में controller authentication पूरा करने के लिए Rack environment में मौजूद cookie jar में session cookie लिखता है. इस समय यह request #3 के लिए cookie jar होता है.

  8. user को request #3 का response मिलता है, लेकिन क्योंकि cookie jar में request #2 की session cookie लिखी गई थी, वह request #2 वाले user के रूप में authenticate हो जाता है.

सार यह है कि अगर exception होने और concurrent request handling का क्रम ठीक इसी तरह बने, तो एक response का session पिछले response से बदल जाता है. यह सिर्फ cookie header में होता था; HTML जैसे response का बाकी हिस्सा मूल authenticated user के data के अनुसार ही रहता था.

यह bug केवल तब होता था जब यह पूरा जटिल sequence एक साथ बनता था.

  • इस समस्या को ठीक करने के लिए नए जोड़े गए background thread को हटाकर 5 मार्च को production में deploy किया गया.

  • इसके बाद Unicorn के लिए patch बनाया गया ताकि environment share न हो, और इसे 8 मार्च को deploy किया गया.

  • logs analysis के बाद पता चला कि यह समस्या बहुत कम हुई थी, लेकिन संभावित जोखिम दूर करने के लिए सभी users के sessions invalidate कर दिए गए.

  • समस्या ठीक करने के बाद Unicorn maintainers के साथ मिलकर यह fix upstream में भी लागू किया गया.

1 टिप्पणियां

 
kunggom 2021-03-22

पैरेलल प्रोसेसिंग वाकई काफ़ी जटिल होती है। मैं भी वीकेंड के दौरान अपनी निजी पढ़ाई के लिए हाल ही में लिखे गए कोड को CPU थ्रेड्स की संख्या के बराबर पैरेलल में चलाने की कोशिश करते हुए काफ़ी देर तक उलझा रहा। आखिरकार यह चल गया, लेकिन क्या यह सच में सही तरीके से काम कर रहा है, इसे लेकर अब भी मुझे थोड़ा संदेह है।