15 poin oleh outsideris 2021-03-21 | 1 komentar | Bagikan ke WhatsApp

Pada 8 Maret lalu, karena kerentanan keamanan, semua pengguna GitHub.com dipaksa logout.

  • Pada 2 Maret, ada laporan bahwa seorang pengguna login tetapi terautentikasi sebagai pengguna lain. Pengguna tersebut segera logout, tetapi melaporkan masalah ini dan penyelidikan pun langsung dimulai. Beberapa jam kemudian, pengguna lain melaporkan isu serupa.

  • Hasil penyelidikan awal menemukan bahwa sesi satu pengguna dibagikan dari 2 alamat IP pada saat laporan dibuat.

  • Saat menyelidiki perubahan infrastruktur terbaru, ditemukan bahwa load balancer dan sisi routing baru saja di-upgrade, dan HTTP keepalives juga telah dimodifikasi di sana, sehingga tampak berhubungan, tetapi setelah diselidiki lebih lanjut ternyata tidak terkait.

  • Namun, dalam proses menyelidiki infrastruktur, diketahui bahwa permintaan yang menerima sesi yang salah diproses tepat pada mesin dan proses yang sama.

  • Setelah meninjau log, diketahui bahwa body respons normal, dan hanya cookie yang dikirim secara salah. Cookie milik pengguna lain yang diproses pada proses yang sama terkirim keliru. Pada salah satu kasus yang dilaporkan, kedua permintaan terjadi berurutan, dan pada kasus lain ada 2 permintaan lain di antara keduanya.

  • Dari sini muncul hipotesis bahwa state bocor antar-permintaan yang diproses dalam proses Ruby yang sama, dan mulai muncul pertanyaan bagaimana hal itu bisa terjadi.

  • Saat meninjau perubahan terbaru, diketahui bahwa demi meningkatkan performa, logika untuk memeriksa fitur yang diaktifkan bagi pengguna diubah agar tidak lagi dijalankan saat memproses permintaan, melainkan di background thread yang memperbaruinya secara berkala. Penyelidikan kemudian difokuskan pada perilaku thread-safe ini.

  • Aplikasi utama GitHub.com adalah Ruby on Rails, dan ada banyak komponen yang tidak ditulis untuk berjalan dalam multi-thread.

  • Thread memang sudah digunakan di aplikasi, tetapi background thread yang baru menimbulkan perilaku tak terduga dalam rutin penanganan pengecualian.

  • Ketika exception terjadi di background thread, log error memuat baik informasi background thread maupun informasi permintaan yang sedang berjalan.

  • Pada awalnya, diperkirakan data dari permintaan yang tidak terkait yang tercatat dalam log dari background thread hanyalah ketidaksesuaian yang muncul karena masalah pelaporan internal.

  • Rails dianggap aman karena membuat objek controller baru untuk setiap permintaan.

  • Karena itu, tetap tidak jelas mengapa masalah ini terjadi.

  • Terobosan mulai terlihat setelah ditemukan bahwa Unicorn, yang digunakan sebagai server HTTP Rack untuk aplikasi Rails, tidak membuat objek env terpisah yang baru untuk setiap permintaan.

  • Sebagai gantinya, Unicorn mengalokasikan hash Ruby untuk setiap permintaan lalu membersihkannya (clear).

  • Dari sini diketahui bahwa log background thread bukanlah ketidaksesuaian pelaporan, melainkan benar-benar menunjukkan bahwa data permintaan dibagikan.

  • Mereka lalu mencoba mereproduksi race condition ini di lingkungan pengembangan, dan menemukan bahwa situasi ini harus dimulai dari permintaan anonim.

  1. Ketika permintaan anonim (permintaan #1) masuk, callback didaftarkan ke library pelaporan exception, dan callback ini menyimpan referensi ke objek controller Rails yang mengakses objek environment permintaan Rack yang disediakan Unicorn.

  2. Ketika exception terjadi di background process, seluruh konteks disalin untuk pelaporan, termasuk callback tersebut.

  3. Di thread utama, permintaan login baru dimulai. (permintaan #2)

  4. Di background thread, pelaporan exception memproses callback konteks. Ia mencoba membaca pengenal sesi pengguna, tetapi karena tidak ada, ia mengirim permintaan ke sistem autentikasi melalui controller Rails dari permintaan #1. Karena Rack menggunakan objek yang sama untuk semua permintaan, controller itu menemukan cookie sesi dari permintaan #2.

  5. Di thread utama, permintaan #2 selesai.

  6. Permintaan login lain masuk. (permintaan #3) Autentikasi sudah selesai.

  7. Di background thread, controller menulis cookie sesi ke cookie jar yang ada di environment Rack untuk menyelesaikan autentikasi. Pada titik ini, itu adalah cookie jar untuk permintaan #3.

  8. Pengguna menerima respons dari permintaan #3, tetapi karena cookie sesi dari permintaan #2 telah ditulis ke cookie jar, ia terautentikasi sebagai pengguna dari permintaan #2.

Singkatnya, bila exception terjadi dan pemrosesan beberapa permintaan tersusun dalam urutan yang menciptakan situasi ini, sesi dari satu respons akan digantikan oleh sesi dari respons sebelumnya. Ini hanya terjadi pada header cookie; respons seperti HTML dan lainnya tetap berisi data untuk pengguna yang semula terautentikasi.

Bug ini hanya terjadi ketika seluruh rangkaian kondisi kompleks ini benar-benar terbentuk.

  • Untuk mengatasi masalah ini, background thread yang baru diperkenalkan dihapus dan dirilis ke production pada 5 Maret.

  • Setelah itu, patch dibuat untuk Unicorn agar environment tidak dibagikan, lalu dirilis pada 8 Maret.

  • Setelah menganalisis log, diketahui bahwa masalah ini jarang terjadi, tetapi untuk mengatasi potensi risiko, sesi semua pengguna dibatalkan.

  • Setelah masalah terselesaikan, mereka bekerja sama dengan maintainer Unicorn agar perbaikan ini juga diterapkan ke upstream.

1 komentar

 
kunggom 2021-03-22

Pemrosesan paralel memang jelas rumit. Akhir pekan ini saya juga sempat cukup lama kebingungan saat mencoba menjalankan kode yang baru-baru ini saya buat secara paralel sebanyak jumlah thread CPU untuk belajar pribadi. Memang berhasil, tapi sampai sekarang saya masih agak tidak yakin apakah hasilnya benar-benar sudah tepat.