1 poin oleh GN⁺ 3 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • Sebagai manajer paket berbasis store, Nix dirancang untuk menempatkan paket pada prefix tetap seperti /nix/store, sehingga menjadi sangat membatasi di lingkungan Nix tanpa root yang ingin menaruh store di lokasi lain tanpa instalasi Nix yang sudah ada atau tanpa hak akses root
  • Jika --store /tmp/... digunakan bersama chroot dan mount namespace, hash yang sama dengan build /nix/store yang sudah ada bisa dipertahankan, sehingga cache binary seperti cache.nixos.org tetap bisa dimanfaatkan
  • Jika prefix store diubah menjadi local?store=/tmp/... tanpa namespace, hash akan berubah, dan bahkan build hello sederhana pun bisa membatalkan seluruh grafik dependensi serta memicu kompilasi ulang GCC
  • Inti usulannya adalah memakai path relatif berbasis $ORIGIN yang didukung dynamic linker Linux, alih-alih path absolut di RUNPATH ELF, agar perubahan lokasi store tidak merambat menjadi perubahan hash dan kompilasi ulang
  • Hambatan utama yang mencegah relokasi nyata adalah kernel tidak mendukung $ORIGIN pada ELF PT_INTERP dan shebang skrip; arah solusi yang diusulkan meliputi patch kernel, wrapper statis, path relatif per bahasa, dan metadata relocatable = true;

Benturan antara prefix store tetap dan Nix tanpa root

  • Sistem berbasis store seperti Nix dan Guix menyimpan semua paket di bawah prefix yang sudah ditentukan
    • Nix memakai /nix/store
    • Guix memakai /gnu/store
  • Struktur ini memudahkan penulisan ulang path binary atau library
    • Misalnya /bin/bash bisa diganti menjadi path store penuh seperti /nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash
  • Ada juga situasi ketika store ingin ditempatkan di lokasi lain
    • lingkungan yang belum memasang Nix
    • lingkungan yang tidak memiliki hak akses yang diperlukan
    • kasus seperti ini mengarah pada masalah “Nix tanpa root”
  • Nix saat ini memang bisa memakai path store lain, tetapi apakah hash tetap sama atau tidak bergantung pada metodenya
    • nix build nixpkgs#hello memasang ke /nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/
    • nix build --store /tmp/fzakaria/store nixpkgs#hello memakai chroot dan mount namespace untuk memasang ke /tmp/fzakaria/store/nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/
    • Pada kedua kasus, hash zi2bj2hlavv8q743li2s9diqbcpmrf9b tetap sama
  • Jika hash sama, derivation yang sudah dihitung sebelumnya dari binary substituter seperti https://cache.nixos.org bisa dimanfaatkan

Biaya saat mengganti store tanpa namespace

  • Alat seperti Bazel atau Buck2 mungkin sudah menggunakan namespace sendiri untuk sandboxing
    • Jika mencoba mengintegrasikan Nix ke ekosistem seperti ini, pembatasan nested user namespace dan mount membuatnya jauh kurang praktis
  • Prefix store alternatif memang bisa ditentukan tanpa chroot dan mount namespace, tetapi ada kelemahan berupa perubahan hash
    • Contoh perintahnya memakai --store 'local?store=/tmp/fzakaria/store&state=/tmp/fzakaria/state&log=/tmp/fzakaria/log'
    • Hasil path hello menjadi /tmp/fzakaria/store/qv3fhi1j9gh27fyds5n5b16yia8i6zn5-hello-2.12.3
    • Hash berubah dari zi2... menjadi qv3fhi1j9gh27fyds5n5b16yia8i6zn5
  • Perubahan sederhana pada string prefix store memicu invalidasi berantai pada seluruh grafik dependensi
    • Hanya untuk menampilkan “Hello World” dari folder lain, Anda bisa berakhir mengompilasi GCC selama 4 jam
    • Dalam kondisi ini, cache publik tidak bisa dimanfaatkan
  • Keterbatasan ini juga sudah dicantumkan dalam dokumentasi Nix saat ini

Bagian yang diselesaikan oleh $ORIGIN dan keterbatasan kernel yang tersisa

  • Akar masalahnya adalah prefix store merupakan bagian dari derivation itu sendiri, sehingga memengaruhi perhitungan hash
  • Jika tidak semua tempat memakai prefix store penuh dan sebagai gantinya menggunakan path relatif, perubahan hash bisa dihindari
  • RUNPATH pada binary ELF adalah salah satu titik yang memungkinkan untuk itu
    • Contoh RUNPATH hello saat ini adalah /nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib
    • Loader Linux mendukung $ORIGIN, yang berarti direktori tempat executable berada
    • Karena itu, RUNPATH bisa ditulis sebagai $ORIGIN/../../57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib
    • Dengan cara ini, lokasi store bisa berubah tanpa mengubah hash dan tanpa perlu kompilasi ulang
  • Namun sebelum dynamic linker membaca RUNPATH, kernel Linux lebih dulu harus memuat dynamic linker itu sendiri
    • Path ini disimpan di header PT_INTERP pada ELF
    • Contohnya /nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib/ld-linux-x86-64.so.2
    • Saat ini kernel Linux tidak mendukung $ORIGIN di PT_INTERP
  • Shebang skrip juga memiliki batasan yang sama
    • Contohnya #!/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash
    • Kernel mengharapkan path absolut saat mem-parsing #!
    • Saat ini shebang juga belum mendukung $ORIGIN
  • Path relatif berbasis direktori kerja saat ini memang bisa dipakai, tetapi akan rusak jika skrip dijalankan dari lokasi lain, sehingga kurang dapat diandalkan

Usulan menuju binary yang dapat direlokasi

  • Untuk benar-benar membuat binary yang dapat direlokasi, batasan kernel perlu diakali atau diubah
  • Ada tiga pendekatan yang diusulkan
    • Mem-patch kernel Linux agar mendukung $ORIGIN pada PT_INTERP dan shebang
    • Membungkus semua binary dengan binary statis kecil, lalu wrapper menghitung lokasinya sendiri sebelum menjalankan dynamic linker
    • Mengubah akses lokasi file agar memanfaatkan fitur path relatif per bahasa
      • Di Python, file bisa diakses relatif terhadap dirinya sendiri lewat __file__
  • Pendekatan yang dinilai paling sesuai adalah memperluas dukungan kernel Linux
    • Pada mesin NixOS, kernel bisa di-patch dengan Nix untuk menambahkan dukungan tersebut
  • Selain itu, diusulkan juga metadata relocatable = true; pada setiap derivation untuk menandai apakah paket dapat direlokasi

1 komentar

 
GN⁺ 3 jam lalu
Komentar Lobste.rs
  • Akan bagus jika kernel Linux menambahkan dukungan $ORIGIN pada PT_INTERP. Dulu saya pernah mencoba dengan biner wrapper statis, dan juga melihat beberapa upaya lain(contoh yang bagus); semuanya adalah hack yang hebat dan keren, tapi pada akhirnya tetap hack
    Saya juga belum benar-benar memahami implikasi keamanannya, jadi akan membantu jika ada penjelasan yang rapi
    Solaris tampaknya mendukung ini, jadi mungkin ada cara untuk melakukannya dengan aman. Sulit menemukan rujukannya, tetapi pada ENOEXEC di manual execve(2) tertulis bahwa jika header program PT_INTERP pada berkas image proses setuid/setgid memiliki path relatif atau memakai token $ORIGIN, maka eksekusi gagal

  • Bukankah banyak perangkat lunak memiliki path yang ditanam saat build atau konstanta, sehingga pada akhirnya tetap harus dikompilasi ulang agar bisa berfungsi dengan benar?

    • Itu memang sudah sering terjadi, terutama pada baris shebang. Nix punya banyak helper untuk mengganti nilai-nilai seperti ini pada saat build
    • Benar, tetapi masalah ini pada dasarnya memang sudah ada terlepas dari repositori lokal. Mungkin derivation turunan akan mengonsumsi outPath melalui {foo}, tetapi jika salah satu dependensi tingkat atas diubah ke repositori lokal, maka harus dibangun ulang
  • Patch dcrt1 untuk musl (ditulis oleh rcombs) menyelesaikan masalah ini di ruang pengguna

  • Apakah tidak bisa membuat sistem berkas yang di-mount ke /origin agar ditafsirkan sebagai $ORIGIN? Kalau begitu, ini sepertinya akan bekerja baik untuk shebang maupun ELF tanpa sintaks tambahan

    • Jika bisa membuat dan me-mount /origin, bukankah kita juga bisa langsung membuat /nix dan menjalankan nix-daemon?
    • Menurut saya, tujuannya kemungkinan adalah agar tetap kompatibel di luar NixOS tanpa perlu memasang atau mengonfigurasi sistem berkas atau daemon tambahan
  • Bukankah akan berisiko dari sisi keamanan jika biner bisa menunjuk loader yang relatif, kemungkinan tidak aman, dan disertakan sendiri?

    • Dalam model ancaman ini, apa yang dipercaya dan apa yang tidak dipercaya? Kalau toh Anda memang akan menjalankan biner itu, apakah maksudnya hanya ingin memastikan ia dijalankan dengan loader yang tepercaya?
    • Mengapa ini harus dianggap kurang aman dibanding variabel lingkungan yang menentukan path pencarian pustaka dynamic linking lain seperti libc.so.6?