15 poin oleh xguru 2023-11-23 | 3 komentar | Bagikan ke WhatsApp
  • Fungsi setenv() dan unsetenv() dalam bahasa C tidak aman digunakan pada program yang memakai thread
  • Fungsi-fungsi ini memodifikasi status global, dan dapat menimbulkan konflik saat thread lain memanggil getenv()
  • Konflik juga terjadi di bahasa lain yang menggunakan fungsi pustaka standar C, seperti os.Setenv di Go dan std::env::set_var() di Rust
  • Dibutuhkan 2 hari untuk melacak masalah terkait dan melaporkan bug di program Go
    • Karena resolver DNS internal Go menggunakan getaddrinfo(), dan itu memanggil getenv()
  • Namun masalah ini sudah sangat lama. Ada tulisan terkait pada 2017, dan di bagian bawah tulisan itu tertulis “sampai jumpa lagi 5 tahun kemudian pada 2022!”, lalu benar-benar bertemu lagi pada 2023
  • Ini adalah cacat pada standar POSIX, yang memperluas standar C agar memungkinkan modifikasi variabel lingkungan
    • Bagian yang paling menjengkelkan adalah, banyak orang yang bisa memengaruhi standar atau memelihara pustaka C tidak menganggap ini sebagai masalah
    • Alasannya karena spesifikasi sudah dengan jelas menyatakan bahwa setenv() tidak boleh digunakan bersama thread
    • Jadi jika seseorang tetap melakukannya, crash dianggap sebagai kesalahan mereka
  • Jadi kita seolah harus “membaca spesifikasi semua fungsi dengan saksama, tidak memakai perangkat lunak yang ditulis orang lain, dan tidak memakai thread”
    • Tetapi ini adalah asumsi yang tidak realistis dalam perangkat lunak modern
    • Daripada itu, kita seharusnya berupaya membuat API yang lebih sulit disalahgunakan dan bisa berevolusi mengikuti perubahan ekosistem
  • Bahasa C dan pustaka standarnya tetap memainkan peran penting sebagai fondasi sebagian besar perangkat lunak, jadi kita harus menemukan cara untuk memperbaikinya atau mencari cara untuk meninggalkannya

Mengapa setenv() tidak Thread-Safe

  • getenv() mengembalikan char*, dan aplikasi tidak perlu membebaskannya nanti
  • Saat satu thread sedang memakai pointer ini, thread lain dapat mengubah variabel lingkungan yang sama dengan setenv() atau unsetenv()
  • Standar C hanya mencakup getenv(), tetapi sebagian besar implementasi mengikuti standar POSIX dan menyertakan fungsi untuk memodifikasi lingkungan
  • putenv() menambahkan char* ke kumpulan variabel lingkungan, dan jika aplikasi memodifikasi memori setelah putenv() mengembalikan hasil, variabel lingkungan itu juga ikut berubah
  • environ adalah array pointer yang diakhiri NULL (char**) yang dapat dibaca dan di-assign oleh aplikasi, dan akses ke array ini tidak thread-safe

Cara implementasi variabel lingkungan

  • Saat aplikasi menimpa variabel yang sudah ada, implementasi harus memutuskan bagaimana menanganinya
  • glibc dan Solaris/Illumos tidak pernah membebaskan variabel lingkungan, sehingga nilai yang dikembalikan dari getenv() bersifat tidak berubah dan bisa dipakai dengan aman antar-thread
  • musl dan FreeBSD/Apple membebaskan variabel lingkungan, sehingga bisa terjadi crash jika pointer yang dikembalikan dari getenv() dipakai setelah thread lain memanggil setenv()
  • Menjamin agar kumpulan variabel lingkungan diperbarui secara thread-safe adalah masalah kedua, dan hal ini menyebabkan crash di glibc

Mengapa program memakai variabel lingkungan

  • Variabel lingkungan berguna untuk mengonfigurasi shared library atau language runtime yang disertakan dalam program lain
  • Pengguna dapat mengubah konfigurasi tanpa penulis program harus secara eksplisit meneruskan konfigurasi tersebut
  • Banyak library memanggil getenv(), dan program perlu mengubah variabel-variabel ini untuk mengonfigurasi library yang mereka gunakan

Masalah ini harus diperbaiki, dan caranya bisa seperti berikut

  • Menurut saya, tidak masuk akal bahwa ini telah lama diketahui sebagai masalah
  • Ribuan jam terbuang untuk men-debug masalah ini atau mendiskusikan cara mengatasinya
  • Cara menyelesaikan masalah ini
    • Membuat implementasi yang thread-safe seperti Illumos/Solaris
      • Ini tetap punya sedikit keterbatasan. Ada kebocoran memori di setenv(), dan tetap tidak aman jika program memakai putenv() atau environ
      • Tetapi ini tetap lebih baik dibanding implementasi Linux dan Apple saat ini
    • Kedua, menambahkan API baru untuk mengambil semua variabel lingkungan yang thread-safe by design, seperti getenv_s() milik Microsoft
  • Solusi yang saya sukai adalah menggunakan keduanya
    • Ini mengurangi kemungkinan masalah pada program dan library lama, sekaligus menyediakan jalur untuk sepenuhnya menghindari masalah pada kode atau bahasa baru seperti Go dan Rust
    • Menambahkan fungsi, mirip getenv_s(), yang menyalin satu variabel lingkungan ke buffer yang ditentukan pengguna
    • Menambahkan API thread-safe untuk mengiterasi semua variabel lingkungan atau menyalin semuanya
    • Menandai getenv() sebagai tidak lagi direkomendasikan, dan sebagai gantinya merekomendasikan fungsi getenv() baru yang thread-safe
    • Menandai putenv() sebagai tidak lagi direkomendasikan, dan sebagai gantinya merekomendasikan setenv()
    • Menandai environ sebagai usang, dan sebagai gantinya merekomendasikan fungsi-fungsi variabel lingkungan
    • Memperbarui implementasi variabel lingkungan agar thread-safe

3 komentar

 
ahwjdekf 2023-11-24

"Karena spesifikasi secara jelas menyatakan bahwa setenv() tidak bisa digunakan bersama thread" ==> saat menggunakan API atau SDK, wajib memeriksa specification/spesifikasi dengan teliti adalah hal yang paling mendasar. Ini terlihat tidak lebih dari sekadar pemaksaan penggunaan.

 
carnoxen 2025-01-24

Masalahnya adalah menggunakan fitur yang sejak awal dirancang dengan buruk

 
cosine20 2023-11-27

....