Geçen 8 Mart'ta, bir güvenlik açığı nedeniyle GitHub.com'daki tüm kullanıcıların oturumu kapatıldı.
-
2 Mart'ta bir kullanıcı giriş yaptığında başka bir kullanıcı olarak kimlik doğrulandığını bildirdi. Kullanıcı hemen çıkış yaptı, ancak bu sorunu rapor etti ve ekip derhal incelemeye başladı. Birkaç saat sonra başka bir kullanıcı da benzer bir sorun bildirdi.
-
İlk inceleme sonucunda, raporun yapıldığı sırada bir kullanıcının oturumunun 2 IP arasında paylaşıldığı görüldü.
-
Yakın zamandaki altyapı değişiklikleri incelendiğinde, kısa süre önce load balancer ve routing tarafında yükseltme yapıldığı ve burada HTTP keepalive ayarlarının değiştirildiği görüldü; bu ilgili göründü, ancak daha fazla araştırma bunun konuyla ilgisiz olduğunu ortaya koydu.
-
Yine de altyapı incelemesi sırasında, yanlış oturumu alan isteğin tam olarak aynı makine ve süreçte işlendiği anlaşıldı.
-
Loglar incelendiğinde, yanıt gövdesinin normal olduğu ve yalnızca cookie'nin yanlış gönderildiği görüldü; aynı süreçte işlenen başka bir kullanıcının cookie'si yanlışlıkla gönderilmişti. Bildirilen vakalardan birinde iki istek art arda gelmişti, diğerinde ise iki istek arasında 2 başka istek vardı.
-
Buradan, aynı Ruby sürecinde işlenen istekler arasında durum sızıntısı olduğu hipotezi kuruldu ve bunun nasıl mümkün olduğu sorgulanmaya başlandı.
-
Yakın zamandaki değişiklikler gözden geçirildiğinde, performansı artırmak için kullanıcıya açık özellikleri kontrol eden mantığın istek işlenirken değil, belirli aralıklarla güncellenen bir arka plan thread'i içinde çalışacak şekilde değiştirildiği görüldü. İncelemenin odağı bu thread-safe davranış oldu.
-
GitHub.com'un ana uygulaması Ruby on Rails ve çoklu thread'de çalışacak şekilde yazılmamış birçok bileşene sahip.
-
Uygulamada thread'ler zaten kullanılıyordu, ancak yeni arka plan thread'i exception işleme rutininde beklenmedik bir davranış oluşturdu.
-
Arka plan thread'inde bir exception oluştuğunda, hata logu hem arka plan thread'inin hem de o anda çalışan isteğin bilgilerini içeriyordu.
-
İlk başta, ilgisiz bir isteğin verisinin arka plan thread'inden loglanması, yalnızca iç raporlama sorunundan kaynaklanan bir tutarsızlık sanıldı.
-
Rails her istek için yeni bir controller nesnesi oluşturup işlediği için bunun güvenli olduğu düşünülüyordu.
-
Bu yüzden sorunun neden ortaya çıktığı hâlâ net değildi.
-
Rails uygulamasında Rack HTTP sunucusu olarak kullanılan Unicorn'un, her istek için yeni ve ayrı bir
envnesnesi oluşturmadığının fark edilmesiyle bir kırılma noktası yaşandı. -
Bunun yerine Unicorn, her istek için bir Ruby hash ayırıyor ve sonra bunu temizliyordu (
clear). -
Böylece, arka plan thread'inin loglarının raporlama tutarsızlığı değil, istek verilerinin gerçekten paylaşıldığının kanıtı olduğu anlaşıldı.
-
Bu race condition'ı geliştirme ortamında yeniden üretmeye çalıştıklarında, durumun ortaya çıkabilmesi için sürecin anonim bir istekle başlaması gerektiği görüldü.
-
Anonim bir istek geldiğinde (istek #1), exception reporting kütüphanesine bir callback kaydedilir; bu callback içinde, Unicorn'un sağladığı Rack istek ortamı nesnesine erişen Rails controller nesnesine bir referans vardır.
-
Arka plan sürecinde bir exception oluştuğunda, raporlamak için tüm context kopyalanır ve callback de buna dahil edilir.
-
Ana thread'de yeni bir giriş yapmış istek başlar. (istek #2)
-
Arka plan thread'inde exception reporting, context callback'ini işler. Kullanıcının oturum tanımlayıcısını okumaya çalışır, ancak bulamaz; bunun üzerine istek #1'in Rails controller'ı üzerinden kimlik doğrulama sistemine istek gönderir. Rack tüm isteklerde aynı nesneyi kullandığı için controller, istek #2'nin oturum cookie'sini bulur.
-
Ana thread'de istek #2 sona erer.
-
Giriş yapılmış başka bir istek gelir. (istek #3) Kimlik doğrulama zaten tamamlanmıştır.
-
Arka plan thread'inde controller, kimlik doğrulamayı tamamlamak için Rack ortamındaki cookie jar içine oturum cookie'sini yazar. Bu anda söz konusu cookie jar, istek #3'e aittir.
-
Kullanıcı, istek #3'ün yanıtını alır; ancak cookie jar'a istek #2'nin oturum cookie'si yazıldığı için, istek #2'nin kullanıcısı olarak kimlik doğrulanır.
Özetle, exception oluşumu ile eşzamanlı istek işleme tam bu sırayla gerçekleşirse, bir yanıtın oturumu önceki yanıtın oturumuyla değiştirilir. Bu yalnızca cookie header'da gerçekleşiyordu; HTML gibi diğer tüm yanıtlar ise başlangıçta kimliği doğrulanmış kullanıcıya ait verilerdi.
Bu hata yalnızca bu karmaşık koşulların tamamı oluştuğunda ortaya çıkıyordu.
-
Bu sorunu gidermek için yeni eklenen arka plan thread'i kaldırıldı ve 5 Mart'ta production'a dağıtıldı.
-
Ardından, ortamın paylaşılmamasını sağlamak için Unicorn'a bir patch hazırlandı ve 8 Mart'ta dağıtıldı.
-
Log analizi sonrasında bu sorunun nadiren yaşandığı görüldü, ancak potansiyel riski ortadan kaldırmak için tüm kullanıcı oturumları geçersiz kılındı.
-
Sorun çözüldükten sonra, Unicorn maintainer'larıyla birlikte bu düzeltmenin upstream'e de uygulanması sağlandı.
1 yorum
Paralel işleme gerçekten de karmaşık. Ben de hafta sonu kişisel çalışmam için yakın zamanda yazdığım kodu CPU iş parçacığı sayısı kadar paralel çalıştırmayı denerken epey zorlandım. Başardım ama düzgün olup olmadığı konusunda hâlâ biraz içim rahat değil.