1 poin oleh GN⁺ 7 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • Keamanan memori memang meningkat secara signifikan, tetapi bahkan pada kode Rust production, masalah penanganan batas sistem tetap ada dan bisa berujung pada kerentanan
  • Alur yang menafsirkan ulang path yang sama di beberapa syscall, cara mengubah izin setelah membuat file, dan perbandingan path berbasis string mudah menimbulkan masalah seperti TOCTOU dan kebocoran izin
  • Di Unix, path, variabel lingkungan, dan data stream berpindah sebagai raw bytes, sehingga pemrosesan yang berpusat pada String atau penggunaan from_utf8_lossy, unwrap, expect dapat menyebabkan kerusakan data atau DoS
  • Jika error dibuang, kegagalan bisa tampak seperti keberhasilan, dan perbedaan perilaku dibanding GNU coreutils juga bisa langsung menjadi masalah keamanan dalam shell script dan alat privileged
  • Dalam audit kali ini, tidak ditemukan bug kategori keamanan memori seperti buffer overflow, use-after-free, atau double-free; risiko utama yang tersisa terfokus bukan di dalam Rust, melainkan pada batas yang bersentuhan dengan dunia luar

Batas Rust yang terungkap lewat audit

  • 44 CVE uutils yang dipublikasikan Canonical menunjukkan bahwa bahkan pada kode Rust production, masih ada kerentanan yang tidak bisa ditangkap oleh borrow checker, clippy, atau cargo audit
  • Pusat masalahnya bukan pada keamanan memori, melainkan pada penanganan batas sistem
    • Ada jeda waktu antara path dan syscall
    • Data byte Unix dan string UTF-8 tidak selaras
    • Ada perbedaan perilaku dengan alat asli
    • Ada penanganan error yang terlewat dan penghentian lewat panic!
  • Daftar CVE ini secara padat menunjukkan titik berakhirnya keamanan pada kode sistem Rust

Menafsirkan path dua kali memicu TOCTOU

  • Jika path yang sama diperiksa di satu syscall lalu dipakai lagi pada syscall berikutnya, hal itu mudah berujung pada kerentanan TOCTOU
    • Di antara dua pemanggilan itu, penyerang yang punya hak tulis ke direktori induk bisa mengubah komponen path menjadi symbolic link
    • Saat pemanggilan kedua, kernel menafsirkan ulang path dari awal sehingga operasi privileged bisa diarahkan ke target pilihan penyerang
  • API std::fs Rust pada dasarnya memakai reinterpretasi berbasis &Path, sehingga kesalahan seperti ini mudah terjadi
  • Pada CVE-2026-35355, alur penghapusan file lalu pembuatan file baru di path yang sama dieksploitasi
    • Di src/uu/install/src/install.rs, setelah fs::remove_file(to)? langsung diikuti File::create(to)?
    • Jika di antara penghapusan dan pembuatan itu to diubah menjadi symbolic link yang menunjuk ke target seperti /etc/shadow, proses privileged dapat menimpa file tersebut
  • Perbaikannya diubah agar memakai OpenOptions::create_new(true) sehingga hanya membuat file baru
    • Menurut dokumentasinya, create_new tidak mengizinkan baik file yang sudah ada maupun dangling symlink di lokasi target
  • Jika harus melakukan dua operasi pada path yang sama, lebih aman mengikatnya ke file descriptor
    • Selain kasus membuat file baru, pendekatan yang tepat adalah membuka direktori induk sekali lalu bekerja dengan path relatif terhadap handle itu
    • Jika ada dua operasi pada path yang sama, anggap itu TOCTOU sampai terbukti sebaliknya

Jangan ubah izin setelah dibuat; tentukan saat pembuatan

  • Alur membuat direktori atau file dengan izin default lalu menjalankan chmod belakangan juga menciptakan jendela paparan singkat
    • Jika ditulis seperti fs::create_dir(&path)? lalu fs::set_permissions(&path, Permissions::from_mode(0o700))?, maka selama jeda itu path ada dengan izin default
    • Pengguna lain bisa melakukan open() dalam jendela waktu tersebut, dan meskipun chmod dijalankan setelahnya, file descriptor yang sudah didapat tidak bisa ditarik kembali
  • Izin harus ditentukan bersamaan dengan pembuatan
    • Gunakan OpenOptions::mode() dan DirBuilderExt::mode() agar objek lahir dengan izin yang diinginkan
    • Kernel tetap akan menerapkan umask, jadi jika pengaruhnya penting, umask juga perlu ditangani secara eksplisit

Perbandingan string path bukan kesamaan filesystem

  • Pemeriksaan awal --preserve-root pada chmod hanya melakukan perbandingan string
    • recursive && preserve_root && file == Path::new("/")
    • Input yang sebenarnya menunjuk ke root tetapi string-nya bukan /, seperti /../, /./, /usr/.., atau symbolic link yang mengarah ke /, bisa melewati pemeriksaan ini
  • Perbaikannya diubah menjadi membandingkan setelah path diurai ke path absolut sebenarnya dengan fs::canonicalize
    • PR perbaikan
    • canonicalize mengembalikan path sebenarnya setelah menyelesaikan .., ., dan symbolic link
  • Untuk kasus --preserve-root, cara ini bekerja karena / tidak punya direktori induk
  • Untuk membandingkan apakah dua path arbitrer merujuk ke objek filesystem yang sama, secara umum yang harus dibandingkan adalah (dev, inode), bukan string
    • GNU coreutils juga menanganinya dengan cara ini
  • Pada CVE-2026-35363, rm menolak . dan .., tetapi tetap menerima ./ dan .///, sehingga direktori saat ini bisa dihapus
    • Jika perbedaan bentuk input hanya ditangani di level string, pemeriksaan mudah disiasati

Di batas Unix, byte lebih penting daripada string

  • String dan &str di Rust selalu UTF-8, tetapi path, variabel lingkungan, argumen, dan data stream di Unix berada di dunia raw bytes
  • Pilihan yang salah saat melintasi batas ini menimbulkan dua jenis bug
    • Konversi lossy seperti from_utf8_lossy diam-diam merusak data dengan mengganti byte yang tidak valid menjadi U+FFFD
    • Konversi ketat seperti unwrap atau ? bisa menolak input atau menghentikan proses
  • CVE-2026-35346 pada comm adalah kasus keluaran rusak akibat konversi lossy
    • Di src/uu/comm/src/comm.rs, byte input ra, rb diubah menjadi String::from_utf8_lossy lalu dicetak dengan print!
    • GNU comm meneruskan byte apa adanya bahkan untuk file biner, tetapi uutils mengubah UTF-8 yang tidak valid menjadi U+FFFD, sehingga output rusak
    • Perbaikannya adalah memakai BufWriter dan write_all untuk menulis raw bytes langsung ke stdout
  • print! memaksa round-trip UTF-8 melalui Display, sedangkan Write::write_all tidak
  • Pada kode sistem keluarga Unix, tipe harus disesuaikan dengan konteks
    • Untuk path file: Path, PathBuf
    • Untuk variabel lingkungan: OsString
    • Untuk isi stream: Vec<u8> atau &[u8]
  • Jika memakai String sebagai perantara demi kemudahan formatting, kerusakan data mudah menyusup

Setiap panic bisa berujung pada denial of service

  • Pada CLI, unwrap, expect, indexing slice, aritmetika tanpa pemeriksaan, dan from_utf8 dapat menjadi titik DoS ketika penyerang bisa mengendalikan input
    • panic! akan melakukan stack unwind dan menghentikan proses
    • Jika sedang dijalankan oleh cron job, CI pipeline, atau shell script, seluruh pekerjaan bisa terhenti
    • Dalam lingkungan eksekusi berulang, crash loop bahkan bisa melumpuhkan seluruh sistem
  • CVE-2026-35348 pada sort --files0-from berhenti saat menemui nama file non-UTF-8 dalam daftar nama file yang dipisahkan NUL
    • Parser memanggil std::str::from_utf8(bytes).expect(...) untuk setiap byte nama
    • GNU sort memperlakukan nama file sebagai raw bytes seperti kernel, tetapi uutils memaksakan UTF-8 sehingga seluruh proses berhenti pada path non-UTF-8 pertama
  • Dalam kode yang memproses input tak tepercaya, unwrap, expect, indexing, dan cast as harus dipandang sebagai calon CVE
    • Gunakan ?, get, checked_*, try_from, dan teruskan error yang nyata ke pemanggil
  • Juga diajukan aturan clippy untuk menangkap hal semacam ini di CI
    • unwrap_used
    • expect_used
    • panic
    • indexing_slicing
    • arithmetic_side_effects
  • Dalam kode pengujian, peringatan seperti ini bisa berlebihan, jadi membatasinya dalam cakupan cfg(test) adalah pendekatan yang masuk akal

Jika error dibuang, kegagalan bisa terlihat sebagai keberhasilan

  • Beberapa CVE muncul karena error diabaikan atau karena informasi error hilang di tengah alur
  • chmod -R dan chown -R hanya mengembalikan exit code file terakhir dari keseluruhan pekerjaan
    • Meski banyak file sebelumnya gagal diproses, jika file terakhir berhasil maka proses bisa berakhir dengan 0
    • Script pun salah menyimpulkan bahwa seluruh operasi selesai tanpa masalah
  • dd memanggil set_len() lalu Result::ok() untuk meniru perilaku GNU pada /dev/null
    • Niatnya adalah membuang error pada kondisi terbatas, tetapi kode yang sama juga diterapkan ke file biasa
    • Bahkan ketika disk penuh, file tujuan yang hanya setengah tertulis bisa diam-diam tertinggal
  • Jika Result dibuang lewat .ok(), .unwrap_or_default(), atau let _ =, penyebab kegagalan yang penting bisa hilang
  • Walaupun tidak berhenti pada kegagalan pertama, program tetap harus mengingat kode error paling serius lalu keluar dengan itu
  • Jika memang harus membuang Result, alasan mengapa aman untuk mengabaikannya harus ditinggalkan di dalam kode

Kompatibilitas yang akurat dengan alat asli juga merupakan fitur keamanan

  • Banyak CVE muncul bukan karena kodenya menjalankan operasi berbahaya, tetapi karena berperilaku berbeda dari GNU
    • Shell script nyata bergantung pada perilaku GNU asli, sehingga perbedaan makna bisa berkembang menjadi masalah keamanan
  • CVE-2026-35369 pada kill -1 adalah contoh khas
    • GNU membaca -1 sebagai signal 1 lalu meminta PID
    • uutils menafsirkannya sebagai mengirim sinyal default ke PID -1
    • Di Linux, PID -1 berarti semua proses yang dapat dilihat, sehingga typo sederhana bisa berubah menjadi kill seluruh sistem
  • Pada alat hasil reimplementasi, kompatibilitas bug-for-bug menjadi pengaman yang mencakup exit code, pesan error, edge case, hingga makna opsi
  • Di setiap titik tempat perilaku berbeda dari GNU, kemungkinan shell script membuat penilaian yang salah ikut meningkat
  • Kini uutils juga menjalankan upstream GNU coreutils test suite di CI
    • Itu tampak sebagai tingkat pertahanan yang tepat untuk mencegah jenis perbedaan seperti ini

Harus menafsirkan lebih dulu sebelum melewati batas kepercayaan

  • CVE-2026-35368 adalah local root code execution pada chroot
  • Pola masalahnya adalah setelah chroot(new_root)?, nama pengguna diurai di dalam root baru yang dikendalikan penyerang
    • get_user_by_name(name)? akhirnya membaca shared library dari filesystem root baru untuk mengurai nama pengguna
    • Jika penyerang menanam file di dalam chroot, hal itu bisa berujung pada eksekusi kode dengan uid 0
  • GNU chroot melakukan penguraian pengguna sebelum chroot
    • Perbaikannya juga diubah ke urutan yang sama
  • Setelah melewati batas kepercayaan sekali saja, setiap pemanggilan library berpotensi mengeksekusi kode penyerang
  • Static linking pun tidak mencegah masalah ini
    • Karena get_user_by_name melewati NSS dan melakukan dlopen modul libnss_* saat runtime

Bug yang benar-benar dicegah Rust

  • Ada jenis bug yang jelas-jelas tidak ditemukan dalam audit kali ini
    • Tidak ada buffer overflow
    • Tidak ada use-after-free
    • Tidak ada double-free
    • Tidak ada data race pada mutable shared state
    • Tidak ada null-pointer dereference
    • Tidak ada uninitialized memory read
  • Sekalipun alat-alat ini memiliki bug, hasil audit tidak menemukan jenis yang bisa dieksploitasi menjadi pembacaan memori arbitrer
  • GNU coreutils dalam beberapa tahun terakhir terus menghasilkan CVE kategori keamanan memori seperti ini
    • pwd deep path buffer overflow
    • numfmt out-of-bounds read
    • unexpand --tabs heap buffer overflow
    • penulisan NUL di luar heap buffer pada od --strings -N
    • pembacaan 1 byte sebelum heap buffer pada sort
    • CVE-2024-0684, yaitu heap overwrite pada split --line-bytes
    • unallocated memory read pada input malformed di b2sum --check
    • stack buffer overrun pada tail -f
  • Dalam perbandingan periode yang sama, reimplementasi Rust mempertahankan 0 bug di kategori ini
    • Meski begitu, audit tersebut juga memberi catatan bahwa ini bukan bukti tidak adanya bug keamanan memori, hanya saja tidak ditemukan
  • Masalah yang tersisa terutama muncul di batas yang bersentuhan dengan dunia luar, bukan di dalam Rust
    • path
    • byte dan string
    • syscall
    • jeda waktu dan perubahan status filesystem

Rust yang benar juga merupakan Rust yang idiomatis

  • Rust idiomatis bukan sekadar kode yang lolos borrow checker dan membuat clippy diam
  • Ketepatan juga harus menjadi bagian dari idiom
    • Karena bentuk kode yang benar-benar bertahan di dunia nyata mengeras lewat pengalaman komunitas
  • Sistem yang tangguh harus mencerminkan kekacauan dunia nyata apa adanya, bukan menyembunyikannya
    • file descriptor alih-alih path
    • OsStr alih-alih String
    • ? alih-alih unwrap
    • kompatibilitas bug-for-bug dengan alat asli alih-alih makna yang tampak lebih bersih
  • Sistem tipe dapat mengekspresikan banyak hal, tetapi tidak bisa menangkap kondisi di luar kendali seperti berlalunya waktu di antara dua syscall
  • Rust idiomatis berarti tipe, nama, dan alur kontrol dalam kode harus mengungkapkan kebenaran lingkungan eksekusi
    • Bentuk yang mungkin kurang cantik dibanding kode rapi di whiteboard, tetapi lebih jujur, justru dibutuhkan

Referensi

1 komentar

 
GN⁺ 7 jam lalu
Opini Hacker News
  • Sebagai maintainer GNU Coreutils, saya membaca artikelnya dengan tertarik, tetapi dari sedikit Rust yang pernah saya pakai, ternyata terlalu mudah membuat TOCTOU race dengan std::fs
    Saya berharap API mirip openat pada akhirnya masuk ke standard library

    Dan saya tidak setuju dengan aturan resolve path sebelum membandingkannya
    Secara umum, lebih baik memanggil fstat lalu membandingkan st_dev dan st_ino, dan poin itu juga sedikit dibahas di artikelnya

    Efek samping yang lebih jarang dipertimbangkan adalah biaya performa
    Sebagai contoh nyata, pada path direktori yang sangat dalam, cp membutuhkan 0.010 detik sedangkan uu_cp membutuhkan 12.857 detik

    Mungkin jarang ada orang yang sengaja membuat path seperti itu di dunia nyata, tetapi perangkat lunak GNU berusaha sangat keras untuk menghindari batas yang sewenang-wenang
    https://www.gnu.org/prep/standards/standards.html#Semantics

    Dan artikel itu mengatakan rewrite Rust memiliki 0 bug keamanan memori selama periode yang kurang lebih sama, tetapi itu tidak benar :)
    https://github.com/advisories/GHSA-w9vv-q986-vj7x

    • Betul, std::fs memang punya masalah lowest common denominator
      Rust 1.0 harus memasukkan sesuatu, dan sayangnya keadaan itu membeku terlalu lama

      Saya melihat uutils sebagai tempat yang bagus untuk mencoba merancang API pengganti std::fs yang lebih sulit disalahgunakan

    • Terima kasih sudah menjelaskan sudut pandang sisi seberang dengan begitu ringkas

      Saya ingin bertanya, pelajaran apa yang seharusnya diambil dari sini
      Untuk ukuran tulisan internet, saya sengaja bertanya dengan cukup agresif, karena dengan adanya kontras kita bisa melihat perbedaan dan kesalahan dengan lebih jelas
      Tentu saja Anda sama sekali tidak berkewajiban menghabiskan waktu atau energi mental untuk menjawab

      Saya penasaran kenapa kecepatan, performa, race condition, dan st_ino terus muncul bersama
      Terlihat seolah latensi, penulisan ke media penyimpanan nyata, atomisitas, ACID, dan kecepatan rambat informasi yang terbatas pada akhirnya mengerucut ke hakikat yang mirip
      Sistem dengan keandalan tinggi seperti akuntansi tampaknya memang pada akhirnya harus menuju ACID, dan sistem dengan keandalan rendah terasa cepat dilupakan sampai perbedaan antar komputer pun terasa tidak terlalu besar

      Saya juga penasaran apakah pada aplikasi sehari-hari throughput benar-benar lebih penting daripada latency

      Dan saya paham kenapa, karena sejarah C, OS keluarga Unix, dan GNU coreutils, fokusnya jatuh pada nomor inode,
      tetapi saya penasaran bagaimana kalau melihat contoh yang sangat dasar seperti sekadar membuat USB drive benar-benar berfungsi dengan baik untuk menyimpan file
      tanpa menghindari kompleksitas seperti buffering I/O libc, fflush, buffering kernel, multi-core, time-sharing, dan beberapa aplikasi yang berjalan bersamaan

    • Saya benar-benar pemula, jadi saya penasaran kenapa perlu loop while alih-alih langsung cd dengan $(yes a/ | head -n $((32 * 1024)) | tr -d '\n')

      Edit: saya mengerti. Karena -bash: cd: a/a/a/....../a/a/: File name too long

    • Entah Anda sudah melihatnya atau belum, ada demo untuk mengonversi otomatis utilitas GNU seperti wget ke subset C++ yang aman terhadap memori
      https://duneroadrunner.github.io/scpp_articles/PoC_autotranslation_of_wget

      Caranya hampir berupa penggantian 1:1 antara elemen C berbahaya dan elemen C++ aman dengan perilaku yang bersesuaian, jadi tampaknya lebih kecil kemungkinan memasukkan bug baru dan perbedaan perilaku baru dibanding rewrite

      Jika kode sumber aslinya dirapikan sedikit, transformasinya bisa sepenuhnya diotomatisasi, sehingga pada tahap build bisa dibuat executable yang sedikit lebih lambat tetapi aman terhadap memori dari sumber C asli

    • Mungkin ini pertanyaan bodoh, tetapi saya penasaran apakah pihak GNU Coreutils sedang mempertimbangkan atau merencanakan rewrite Rust mereka sendiri

  • Mereka mungkin tahu cara memakai Rust, tetapi tidak cukup akrab dengan API Unix beserta semantik dan jebakannya
    Sebagian besar kesalahan itu, dari sudut pandang pengembang lama GNU coreutils atau BSD, Solaris, tergolong cukup pemula
    Banyak masalah semacam itu sudah terungkap dan dibereskan puluhan tahun lalu, dan walau di codebase lama masih ada ekor panjang perbaikan sampai sekarang, kini umumnya hanya sedikit yang terus masuk

    • Saya benar-benar tercengang membaca thread Canonical itu
      Intinya kira-kira, “Rust lebih aman, keamanan adalah prioritas utama, jadi rollout rewrite seluruh coreutils itu mendesak. Kalau ada yang rusak tidak apa-apa, nanti diperbaiki.”

      Saya tidak ingin menjalankan kode buatan orang yang berpikir seperti itu di mesin saya
      Saya juga pro-Rust, tetapi bahwa Rust lebih aman itu hanya berlaku jika faktor lain setara
      Di sini faktor lain sama sekali tidak setara

      Rewrite pada akhirnya pasti memiliki jauh lebih banyak bug dan kerentanan dibanding kode yang sudah dikelola puluhan tahun, jadi argumen keamanan mungkin masuk akal untuk strategi transisi jangka panjang, tetapi tidak bisa dijadikan dasar rollout yang terburu-buru

      Sikap yang mengecilkan dampak ke pengguna setelah rilis, atau berkata “bug memang akan kelihatan kalau begini”, “coreutils lama juga tidak punya pengujian yang layak”, itu terlalu tidak bertanggung jawab
      Pengguna bukan kelinci percobaan
      Saya rasa maintainer punya tanggung jawab moral untuk tidak merusak keandalan sistem pengguna

    • Lebih mendasarnya lagi, standard library Rust tampak mendorong developer ke API yang rapi tetapi berada di tingkat abstraksi yang salah
      Misalnya ke operasi file berbasis path alih-alih berbasis handle
      Saya berharap saya salah

    • Menurut saya inti Rust adalah membuat jebakan terbesar dan paling mudah dimasuki jadi tidak perlu terlalu dipikirkan

      Tampaknya inti artikel ini pada akhirnya adalah bahwa API filesystem juga harus melakukan hal itu

    • Seseorang pernah membuat istilah disassembler rage untuk menggambarkan hal seperti ini
      Maksudnya, kalau diperhatikan dari cukup dekat, semua kesalahan akan terlihat seperti kesalahan amatir

      Istilah itu juga muncul dari sikap menatap disassembler lalu memarahi programmer tingkat tinggi karena memakai if alih-alih switch di fungsi yang berada 100 frame call stack di bawah

      Sekarang kita hanya melihat beberapa hal yang salah dari mereka, dan hampir tidak melihat ribuan baris kode yang ditulis dengan benar di sekitarnya

    • Terjadinya panic pada utilitas seperti ini, bahkan menurut standar Rust sekalipun, adalah kesalahan yang cukup amatir
      Kalau itu error alokasi yang tak bisa ditangani mungkin lain cerita, tetapi expect dan unwrap sulit dibela kecuali ada invariant yang benar-benar dijaga ketat sehingga jalur kode itu pasti tidak akan pernah dieksekusi

  • Salah satu hal sulit dalam me-rewrite kode adalah bahwa kode asli telah berubah secara bertahap sambil menanggapi masalah yang hanya muncul di lingkungan operasi nyata

    Pelajaran yang diperoleh dalam proses itu diam-diam meresap ke dalam kode, dan jika tidak terdokumentasi, ada sangat banyak pekerjaan tersembunyi yang harus dilakukan sebelum mencapai tingkat kesetaraan yang sama

    Tulisan aslinya menunjukkan daftar jenis hal seperti itu dengan sangat baik

    Namun sebelum langsung menyebutnya amatir, kita juga perlu melihat bahwa ini adalah salah satu gejala paling “software banget” dalam software
    Kecuali mereka mengabaikan dokumentasi teknis yang sangat bagus dan test untuk kasus-kasus itu di coreutils, kejadian seperti ini hampir tak terelakkan

    • Contoh bagus dari artikel itu adalah chroot + NSS CVE
      Fakta bahwa NSS itu dinamis, dan bahwa di dalam chroot ia akan dlopen library, tidak tertulis secara mencolok di mana pun

      Itu lebih mirip pengetahuan yang dipelajari para sysadmin lewat pengalaman pahit selama lebih dari 25 tahun, dan rewrite clean-room biasanya mempelajarinya ulang sebagai CVE baru
      Kalau kode yang sama di-porting dengan LLM pun situasinya serupa
      Signature fungsi bisa dibaca, tetapi yang benar-benar dibutuhkan adalah luka dan bekas luka yang tertinggal di kode itu

    • Kalau pekerjaan ini dilakukan sambil menghindari GPL dan tanpa membaca source asli, itu jadi lebih sulit lagi

      Menurut saya uutils akan jauh lebih baik kalau berlisensi GPL dan boleh mengambil inspirasi langsung dari source asli coreutils

    • Perlu ditegaskan juga bahwa tidak mendokumentasikan pelajaran seperti ini, atau setidaknya bug dan kerentanan yang berusaha dihindari, adalah praktik yang buruk

      Tentu sulit mendokumentasikan semua bug yang secara implisit dihindari hanya karena kodenya ditulis dengan baik sejak awal,
      tetapi bagi pembaca di masa depan, penting untuk meninggalkan penjelasan seperti “alasan memakai foo di sini alih-alih bar adalah karena jika bar dipakai dalam kondisi ABC maka akan muncul baz berbahaya akibat XYZ”
      Menurut saya itu lebih baik, meskipun tampak seperti sedikit pemborosan waktu dan ruang dokumentasi

  • Banyak hal yang ditunjuk artikel ini terasa, terutama jika dibandingkan dengan source GNU coreutils, seharusnya bisa tertangkap oleh unit test atau review manual yang lumayan
    Rewrite coreutils tampak seperti ide yang buruk
    https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/
    dan sepertinya dilakukan dengan cara yang salah, tanpa cukup membawa pengetahuan yang telah dikumpulkan perangkat lunak sebelumnya

    Kalau ingin me-rewrite, Anda harus benar-benar memahami dan belajar dari pendahulunya
    Kalau tidak, Anda akan mengulang kesalahan yang sama, dan terus terang itu cukup memalukan

    Biar jelas, saya suka Rust, memakainya di beberapa proyek, dan Rust itu hebat
    Hanya saja Rust tidak akan menyelamatkan rekayasa yang buruk

    • Menariknya, uutils memang memakai test suite GNU coreutils

      Tambahan lagi, mereka juga sudah menyatakan bahwa kontribusi yang ditulis setelah membaca source GPL tidak akan diterima

    • Dari pihak yang membuat unity, upstart, dan snap, hal seperti ini justru masih dalam kisaran yang bisa diperkirakan

    • Mungkin ini sambutan yang perlu diberikan kepada programmer sistem baru
      Unix itu rusak, dan pada akhirnya Anda harus menulis solusi jelek yang tidak indah dan tidak mendidik sendiri, serta melakukan pengujian empiris
      Begitulah cara kerja perangkat lunak yang dapat dipercaya dan rekayasa perangkat lunak yang baik

  • Saya penasaran kenapa differential fuzzing tidak menangkap bug-bug seperti ini

    https://github.com/uutils/coreutils/tree/main/fuzz/uufuzz

  • Pola memeriksa sebuah path lewat syscall sekali, lalu melakukan syscall lagi pada path yang sama untuk bekerja dengannya, selalu memanggil masalah yang sama
    Penyerang yang punya izin tulis pada direktori induk bisa menukar komponen path itu dengan symbolic link di sela-selanya, dan kernel akan me-resolve ulang path dari awal pada panggilan kedua, sehingga operasi berhak istimewa itu diarahkan ke target yang dipilih penyerang

    • Sebenarnya lebih buruk dari itu
      Penyerang yang punya izin tulis pada direktori induk juga bisa bermain-main dengan hard link
      Bahkan kalau Anda hanya bisa menyentuh file reguler, pada praktiknya hampir tidak ada mitigasi yang layak
      Untuk contohnya lihat https://michael.orlitzky.com/articles/posix_hardlink_heartache.xhtml
    • Hmm… mungkin ada cara memasang write lock pada direktori, tetapi begitu masalah seperti timeout ikut terlibat, sepertinya itu akan cepat jadi lebih rumit lagi
  • Akar penyebab beberapa bug ini tampaknya adalah API Unix yang terlalu tidak transparan

    Misalnya fakta bahwa get_user_by_name memuat shared library di dalam root filesystem baru untuk menyelesaikan nama pengguna, dan karenanya penyerang yang bisa menanam file di dalam chroot dapat mengeksekusi kode sebagai uid 0, terasa hampir seperti jebakan booby trap

    Fungsi untuk mengambil data pengguna tiba-tiba juga memuat shared library tampak seperti desain yang mencampuradukkan concern
    Menurut saya pengambilan data pengguna dan pemuatan library seharusnya dipisahkan di tingkat fungsi, atau setidaknya perilaku semacam itu harus terlihat jelas dari namanya

    • Sebagiannya mungkin benar, tetapi kalau Anda memutuskan menulis ulang coreutils dari nol, maka memahami API POSIX itu sendiri secara harfiah adalah pekerjaan inti

      Lagi pula, kalau kode yang memeriksa apakah suatu path menunjuk ke root filesystem adalah file == Path::new("/"), itu bukan masalah API
      Orang yang menulis seperti itu rasanya nyaris tidak layak ikut proyek ini

    • Saya malah merasa memakai bahasa aman fungsional bisa membuat orang mengira data yang mereka tangani juga stateless
      Padahal dalam sistem operasi, sangat banyak hal yang terus berubah

      Sampai filesystem yang menyediakan snapshot menjadi umum, semua hal harus terus diperiksa ulang

      Pada akhirnya yang dibutuhkan adalah API yang ketika diberi input hanya mengembalikan hasil sukses atau gagal, bukan API yang memberi tiga kemungkinan: sukses, gagal, atau error

    • Betul, musl libc memang menghilangkan tepat satu bagian seperti itu

    • Menurut saya akar masalahnya bukan ketidaktransparanan API Unix, melainkan tidak benar-benar memikirkan situasi ketika root melakukan chroot ke direktori yang tidak ia kendalikan

      Apa pun yang menjadi target chroot berada di bawah kendali pihak yang membuat chroot itu, dan kalau Anda tidak memahami itu, Anda tidak layak memakai chroot()

      get_user_by_name mungkin memang terasa seperti jebakan, tetapi sebenarnya hampir tidak ada perbedaan nyata antara memakai newroot/etc/passwd dan memakai newroot/usr/lib/x86_64-linux-gnu/libnss_compat.so, newroot/bin/sh, dan semacamnya

      Karena itu saya rasa /usr/sbin/chroot seharusnya sejak awal tidak punya alasan untuk mencari user ID
      toybox chroot juga tidak melakukannya
      Jadi bug-nya bukan pada cara melakukan sesuatu yang salah, melainkan pada fakta bahwa pekerjaan itu dilakukan sejak awal

    • Unix dan POSIX itu seperti fraktal; dipotong di mana pun tetap penuh jebakan

  • Bahkan kalau orang-orang Rust itu menulis ulang coreutils tanpa pengalaman Linux, saya justru lebih tidak paham bagaimana Ubuntu bisa menerimanya ke mainline

    • Ubuntu seolah punya kebijakan mengganti satu komponen fondasi sistem di hampir setiap rilis dengan eksperimen setengah matang dan belum selesai

      Menurut saya intinya di sini bukan “wah, ternyata ada bug di kode Rust”, melainkan justru itu

    • Yang asli berlisensi GPL, dan versi rewrite berlisensi MIT

  • Jika pernyataan “bug-bug ini muncul di kode Rust yang benar-benar dirilis, dan penulisnya juga orang-orang yang tahu apa yang mereka lakukan” itu benar,
    saya jadi penasaran apakah itu berarti utilitas aslinya juga tidak punya test harness, dan rewrite-nya juga tidak dimulai dengan membuat itu lebih dulu

    Walaupun edge case-nya banyak, rasanya OS dan FS masih bisa diabstraksikan sampai tingkat tertentu untuk memverifikasi hal-hal seperti apakah rm .// benar-benar tidak menghapus direktori saat ini seperti yang diharapkan

    Ini tampak bukan soal coding yang berantakan atau kritik pada bahasa, melainkan sekali lagi sikap lama bahwa pemrograman sistem tidak dites

    Sebaliknya, kalau utilitas aslinya memang punya test tetapi tetap ada banyak celah seperti ini, mungkin test suite aslinya sendiri sangat kurang

    • Saya kira begitu

      Tetapi saya tidak terlalu yakin bahwa OS dan FS bisa cukup diabstraksikan untuk diverifikasi sejauh itu
      Alasannya, orang-orang sudah mencoba melakukannya sejak sebelum saya lahir dan tampaknya masih belum berhasil

      Misalnya, sejak awal saja tidak jelas bagaimana menentukan berapa banyak / yang perlu ditempelkan untuk diuji

      Lebih jauh lagi, misalkan rm menolak menghapus jika 9 byte pertama file adalah important,
      membayangkan bagaimana membuat test yang bisa menemukan perilaku seperti itu tanpa terlebih dahulu mengetahui string tersebut terasa sulit
      Bahkan akan lebih sulit lagi kalau kata ajaib itu adalah string yang tidak ada di kamus

      Saya hampir tidak pernah melihat orang yang dengan serius berkata “pemrograman sistem tidak dites”
      Yang lebih sering saya dengar adalah bahwa test tidak selalu melakukan peran yang orang harapkan

    • Sejauh yang saya pahami, dalam proses pengembangan uutils memang ada pengujian perbandingan perilaku yang luas terhadap utilitas asli, dan mereka bahkan berusaha mempertahankan bug-bugnya juga

    • Ini salah satu alasan kenapa Windows secara default menonaktifkan symlink
      Bukan menyelesaikannya lewat abstraksi, melainkan dengan secara efektif menghapus fitur itu sendiri

      Di dunia Unix, sudah terlalu banyak perangkat lunak yang bergantung pada symlink selama puluhan tahun sehingga hal seperti itu tidak bisa dilakukan

      MacOS juga punya respons yang mirip
      Misalnya bug chroot() secara default jarang benar-benar jadi masalah, karena MacOS pada dasarnya memblokir chroot() secara default
      Untuk memakainya Anda harus mematikan system integrity protection

      Masalah dasarnya ada pada sudut tajam API POSIX, dan solusinya lebih mendekati menghapusnya daripada mengabstraksikannya

  • Menurut saya tidak masalah kalau orang bereksperimen dan mencoba dengan canggung
    Memang begitulah cara belajar dan berkembang

    Yang benar-benar membuat saya penasaran adalah bagaimana rantai pengambilan keputusan di Ubuntu bisa rusak sampai hal seperti ini masuk ke produksi

    • Kadang berkembang itu cuma berarti tubuhnya makin besar