2 poin oleh GN⁺ 2024-03-27 | 1 komentar | Bagikan ke WhatsApp
  • Firmware network switch Oxide tidak menyala setelah pengujian perubahan power sequencing, dan penyebabnya adalah bug pada pemeriksaan peminjaman memori IPC di kernel Hubris yang berbenturan dengan cara penataan memori baru
  • Hubris adalah sistem operasi embedded yang mengisolasi task dengan MPU, dan saat sebuah task meminjamkan memori ke task lain lewat IPC, kernel memeriksa apakah memori tersebut benar-benar berada di area yang dapat diakses
  • Task packing yang baru diperkenalkan berhasil merebut kembali 30% RAM pada sebagian image firmware, tetapi pemeriksaan lama gagal karena berasumsi memori yang dipinjamkan berada di dalam satu area MPU tunggal
  • Task sequencer mati karena synthetic memory fault saat mencoba meminjamkan memori yang mencakup alamat 0x801bffd ke driver I2C, dan humility tasks mencatat 115 kali restart serta status mem fault... in syscall
  • Perbaikannya dilakukan dengan mengubah algoritme pemeriksaan agar mengizinkan peminjaman yang melintasi beberapa area MPU yang bersebelahan, dan butuh sekitar 3 jam dari ditemukannya gangguan hingga perbaikan bug kernel

Network switch yang tidak mau menyala

  • Arjen Roodselaar dari Oxide sedang menguji perubahan power sequencing dan pengaturan clock pada firmware network switch ketika, setelah perubahan yang tampak sepele, ia menemui masalah switch tidak mau menyala
  • Sebagian firmware merespons query, tetapi bagian penting yang bertanggung jawab atas power supply sequencer tampak berhenti
  • Kesalahan power sequencing bisa benar-benar merusak hardware, jadi hal pertama yang harus dipastikan adalah apakah switch memang mati atau hanya tidak merespons

Hubris dan memori terbatas

  • Hubris adalah sistem operasi untuk sistem embedded yang sangat dalam, seperti controller internal keyboard, dan dibuat untuk menangani pekerjaan yang diperlukan untuk memulai prosesor besar di Oxide Rack
  • Firmware berbasis Hubris terdiri dari beberapa program yang dikompilasi terpisah, disebut task
    • Setiap task memiliki sendiri kode standard library yang dibutuhkannya, dan sebagainya
    • Task dirancang agar diisolasi oleh MPU hardware sehingga tidak saling membuat crash atau merusak memori satu sama lain
  • Pada ARMv7-M dari keluarga ARM Cortex-M yang terutama digunakan, area memori terlindungi harus berukuran pangkat dua dan disejajarkan sesuai ukuran tersebut
    • Misalnya, jika area 1024 byte membutuhkan tambahan 1 byte, hasilnya bukan area 1025 byte, melainkan area 2048 byte

Batas baru yang dibuat oleh task packing

  • Hubris awalnya memakai pendekatan sederhana: satu area untuk RAM task dan satu area untuk flash, tetapi ini membuang memori karena muncul ruang kosong yang tidak bisa dipakai di antara task
  • Matt Keeter meningkatkan build system agar, jika memungkinkan, ia menempatkan task dengan menggabungkan beberapa area power-of-two
    • Hardware hanya mengizinkan maksimal 8 area per task
    • Pada sebagian image firmware, 30% RAM berhasil direbut kembali
    • Perangkat terkecil yang sebelumnya begitu ketat hingga butuh optimasi setiap kali kini mendapat ruang lega
  • Perubahan ini memungkinkan munculnya batas area MPU yang sulit diprediksi di tengah flash dan RAM sebuah task

Petunjuk yang ditinggalkan humility tasks

  • Arjen menyelidiki switch yang gagal dengan Humility, debugger Hubris, dan karena service processor yang menangani power sequencing masih hidup dan berjalan, kemungkinan masalah hardware tampak rendah
  • Dalam output humility tasks, task sequencer menunjukkan status berikut
mem fault (precise: 0x801bffd) in syscall (was: wait: reply from i2c_driver/gen0)
  • Task yang sama sudah restart 115 kali, dan di Hubris task hampir selalu direstart sebagai respons terhadap crash
  • String status tersebut berarti:
    • mem fault: pelanggaran aturan penanganan memori
    • precise: 0x801bffd: alamat spesifik yang bermasalah dapat diketahui
    • in syscall: task sedang berada di dalam system call, bukan sedang mengeksekusi kode biasa
    • was: wait: reply from i2c_driver/gen0: task sedang menunggu respons dari pesan yang dikirim ke driver I2C
  • gen0 berarti i2c_driver belum pernah crash, sementara sequencer berada pada generation 115

IPC Hubris dan peminjaman memori

  • Task Hubris berkomunikasi lewat pesan IPC, dan pesan bekerja seperti pemanggilan fungsi
    • Task yang mengirim pesan berhenti
    • Task penerima mengambil alih kendali CPU
    • Ketika hasil kembali, task pengirim dibangunkan lagi
  • IPC dirancang agar selaras dengan model ownership Rust, sehingga task dapat meminjamkan sebagian memorinya kepada task lain bersama pesan IPC
  • Task yang berinteraksi dengan perangkat I2C meminjamkan rentang memorinya sendiri kepada driver bus I2C, dan driver membaca atau menulis rentang itu di tempat
    • Kebutuhan driver bus untuk memiliki buffer pool terpisah berkurang
    • Jumlah penyalinan data berkurang
  • Jika diimplementasikan keliru, ini bisa menjadi celah keamanan, sehingga kernel Hubris melarang task meminjamkan memori yang sebenarnya tidak dimiliki atau tidak dapat diaksesnya
    • Server menerima kode error
    • Client menerima fault dan selalu dihentikan
    • Ini diperlakukan sebagai pelanggaran akses yang menunjukkan kemungkinan bug, korupsi, atau exploit

Synthetic fault dan penyebab sebenarnya

  • Hubris membedakan fault menjadi real fault dan synthetic fault
    • Real fault adalah pelanggaran aturan hardware, seperti dereference null pointer atau menulis ke area kode
    • Synthetic fault adalah pelanggaran aturan software tambahan yang dibuat Hubris, seperti IPC atau peminjaman memori
  • Fault pada sequencer adalah synthetic fault yang terjadi dalam proses meminjamkan memori ke driver I2C lewat IPC
  • Alamat bermasalah 0x801bffd adalah alamat flash yang valid, tetapi menunjukkan pola aneh karena berada 3 byte di bawah batas pangkat dua
  • Output humility mem menunjukkan dua area flash milik task sequencer bertemu di 0x801c000
LOW         HIGH           SIZE ATTR   ID TASK
0x08018000 - 0x0801bfff   16kiB r-x--- 17 sequencer
0x0801c000 - 0x0801dfff    8kiB r-x--- 17 sequencer
  • Karena kedua area itu milik task yang sama, dalam eksekusi program normal MPU hardware dapat mengizinkan akses tanpa masalah, tetapi pemeriksaan peminjaman memori IPC di kernel memiliki asumsi berbeda

Titik ketika penyederhanaan lama menjadi bug

  • Pemeriksaan kernel lama hanya memastikan apakah seluruh slice memori yang hendak dipinjamkan sepenuhnya berada di dalam satu area milik task
self.region_table().iter().any(|region| {
    region.covers(slice)
        && region.attributes.contains(desired)
        && !region.attributes.intersects(forbidden)
})
  • Kode ini cocok dengan desain saat ia ditulis, yaitu asumsi satu area RAM dan satu area flash per task
  • Setelah task packing diperkenalkan, memori task yang sama bisa terbagi ke beberapa area MPU yang bersebelahan, sehingga asumsi lama tidak lagi berlaku
  • Akses memori biasa tidak terdampak karena diperiksa langsung oleh MPU hardware; masalah baru muncul hanya saat memori tersebut hendak dipinjamkan lewat IPC

Gangguan yang dibuat oleh dua fitur bersama-sama

  • Task packing bekerja secara oportunistis
    • Ada batas maksimal 8 area per task
    • Task driver hardware sudah memakai sebagian area karena register memory-mapped
    • Penempatan yang lebih cerdas hanya dicoba ketika masih ada slot area yang tersisa
  • Akibatnya, batas area muncul di posisi yang sulit diprediksi oleh penulis task
  • Perubahan kecil pada ukuran task A dapat mengubah posisi batas area MPU pada task B yang tidak terkait
  • Menambahkan kode debugging saja bisa mengubah keputusan penempatan dan batas area, sehingga crash dapat menghilang
  • Matt segera mematikan task packing di build system agar Arjen dapat membuat image firmware yang berjalan, sementara analisis dan perbaikan bug kernel dilakukan secara paralel

Cara perbaikan kernel

  • Inti perbaikannya adalah mengubah algoritme pemeriksaan akses memori agar memori yang hendak dipinjamkan tetap diizinkan meskipun melintasi beberapa area MPU yang benar-benar bersebelahan
  • Algoritme baru dirancang agar hanya menyapu tabel area satu kali
    • Hubris berusaha menghindari mengekspos operasi dengan kompleksitas waktu yang dapat dikendalikan task
    • Performa harus bergantung hanya pada tabel area yang ukurannya tetap, bukan pada ukuran memori yang dipinjamkan
    • Ukuran tabel area tetap 8
  • Untuk itu, build system diubah agar mengurutkan area task berdasarkan alamat menaik
regions.sort_by_key(|i| region_table.get_index(*i).unwrap().1.base);
  • Commit perbaikan membuat kernel memanfaatkan properti pengurutan ini untuk pemeriksaan akses yang lebih murah
  • Kode yang menjadi lebih kompleks dipisahkan dari inti kernel Hubris dan dipindahkan ke crate yang lebih portabel, dan unit test untuk corner case penting ditambahkan
  • Dengan kode baru, task packing dapat diaktifkan lagi, tanpa meninggalkan crash yang sulit diprediksi bagi developer task

Mengapa gangguan tidak menyebar luas

  • Alur keseluruhan dimulai dari network switch yang tidak mau menyala dan berujung pada perbaikan bug kernel dalam sekitar 3 jam
  • Berkat fault isolation, dari firmware switch yang terdiri atas 23 task terisolasi, hanya sequencer yang berulang kali mati, sementara banyak komponen lain tetap berjalan
    • Sistem update firmware
    • Stack jaringan IP untuk antarmuka manajemen dan kontrol
    • Berbagai layanan jaringan, dari implementasi protokol echo hingga antarmuka control plane rack
    • I2C, SMBus, PMBus untuk pemantauan sensor, kipas, dan status sistem lain
    • Driver untuk 32 transceiver QSFP 100G di panel depan
  • IPC Hubris dirancang dengan asumsi task lain bisa gagal, sehingga operasi yang ditandai idempotent dapat dicoba ulang secara transparan
  • Bug pemeriksaan akses memori lama berbentuk memblokir akses program yang benar, bukan mengizinkan akses yang keliru atau berbahaya, sehingga tidak ada dampak keamanan
  • Saat sequencer mati pada momen ketika ia dan driver I2C pada dasarnya sedang berbagi memori, driver I2C tetap berjalan tanpa risiko korupsi

Infrastruktur debugging dan operasi tim

  • Humility adalah debugger yang berkembang bersama kernel Hubris, dan dalam beberapa menit Arjen dapat mengidentifikasi lokasi kode yang crash hingga tingkat nomor baris serta membagikan snapshot independen dari service processor
  • Hubris mencatat compressed core dump dari task yang crash ke RAM, dan dapat mengambilnya lewat jaringan
    • Crash dump tetap bisa diperoleh meski tidak ada persistent storage yang bisa ditulis
    • Fitur crash dump berada di task terpisah, bukan di kernel
  • Prosesor-prosesor tersebut tidak menangani data workload pelanggan, hanya traffic manajemen sistem, dan laporan crash tidak diunggah otomatis
  • Bagian kernel Hubris yang independen dari arsitektur berukuran 1.789 baris kode dan 1.192 baris komentar, sementara dukungan ARMv6-M, ARMv7-M, dan ARMv8-M menambahkan 1.075 baris kode dan 534 baris komentar
  • Konsep kernel dan IPC yang sederhana membuat tidak banyak tempat yang perlu diperiksa saat fault mengarah ke IPC

1 komentar

 
GN⁺ 2024-03-27
Opini Hacker News
  • Hubris benar-benar bagus. Saya membaca kode kernelnya sekitar 30 menit, dan ini jauh dari kode C yang pernah saya lihat dulu—penuh makro ifdef, suka nama variabel dua huruf, dan minim komentar. Kodenya sangat jelas dan ditulis dengan baik.
    Cocok juga sebagai bacaan sebelum tidur, dan saya sarankan untuk sekadar melihat-lihat: https://github.com/oxidecomputer/hubris/blob/b44e677fb39cde8...

    • Saya merasa cukup terganggu karena sebagian besar budaya C seolah bisa diringkas menjadi “malas belajar mengetik dengan kecepatan yang layak.”
      Ruang disk untuk source code sudah bukan masalah besar sejak 40 tahun lalu, tetapi orang masih pelit memakai nama variabel
    • AI bisa saja menghapus kebiasaan ini. Masukkan kode C lama yang kasar ke AI, dan tiba-tiba semua variabel bisa dirapikan serta dinamai sesuai cara yang disukai pengguna.
      Itu karena AI telah mempelajari preferensi dan kebiasaan coder tertentu dengan tepat
  • Tulisan yang bagus, tetapi posisi komentar berikut ini agak disayangkan.
    Komentar di atas regions.sort_by_key(|i| region_table.get_index(*i).unwrap().1.base); yang berbunyi “harus diurutkan menaik berdasarkan alamat, dan kernel memanfaatkan properti ini untuk membuat pemeriksaan akses murah” lebih merupakan invarian field yang harus dipatuhi semua penulis dan dapat dimanfaatkan semua pembaca, bukan detail fungsi ini.
    Jadi sepertinya lebih tepat dimasukkan ke doc string TaskDesc::regions: https://github.com/oxidecomputer/hubris/commit/b44e677fb39cd...

    • Meski begitu, bagus juga ada komentar di dekat kode pengurutan. Kalau tidak, pengurutan itu sendiri bisa terlihat cukup mengejutkan.
      Mungkin cara terbaik adalah membuat metode konstruktor pada TaskDesc yang mengurutkan region sehingga invarian tersebut dipaksakan. Terlihat kodenya makin kompleks seiring waktu, jadi sekarang tampaknya layak meluangkan sedikit waktu untuk membungkus kompleksitas itu di dalam metode
  • Ini salah satu lowongan kerja terbaik yang pernah saya lihat. Alurnya bagus: secara alami masuk ke pembahasan budaya, lalu di akhir ada “ngomong-ngomong, kami sedang merekrut.”
    Ini tulisan postmortem yang benar-benar hebat, dan saya sebagai developer lapisan aplikasi pun bisa mengikutinya. Kebetulan saya sedang membaca Rust in Action, jadi saya juga lebih siap untuk materi seperti ini.
    Selalu menyenangkan melihat orang yang banyak memberi komentar pada kode. Literate programming memang berhasil

    • Sayangnya ini hanya berlaku di AS
  • Sepertinya bagian sebelumnya bisa dilihat di sini.

    1. https://hachyderm.io/@mjk/112157472314396711
    2. https://www.mattkeeter.com/blog/2024-03-25-packing/
  • Bagian “integrasi tim yang rapat dan non-hierarkis” menarik perhatian saya. Itu bukan fitur Hubris itu sendiri, tetapi penjelasan bahwa sulit memisahkan Hubris dari tim yang membuatnya, dan bahwa tim engineering Oxide pada dasarnya tidak memiliki silo internal, terasa mengesankan.
    Saya ingin mendengar lebih banyak tentang mengapa mereka membangun budaya yang mendorong keterbukaan, rasa ingin tahu, dan komunikasi, sambil menekan sikap defensif, pembangunan kerajaan kecil, dan gatekeeping, serta bagaimana mereka menerapkannya secara konkret. Saya juga penasaran apakah ada kekurangan saat menumbuhkan budaya seperti ini di dalam organisasi.
    Ada juga tempat yang memilih sistem hierarkis yang lebih ketat, dan bagan organisasi mungkin perlu ditentukan secara strategis, jadi saya belum benar-benar menangkap trade-off-nya

    • Nilai-nilai yang dinyatakan itu sendiri sulit dinilai, tetapi secara umum kekurangan dari organisasi tanpa struktur yang didefinisikan kuat adalah tetap akan muncul suatu bentuk struktur kekuasaan.
      Jika struktur itu tidak dinyatakan secara eksplisit, ia menjadi kurang terbuka, bukan sesuatu yang dipilih secara sengaja, dan lebih sulit dipahami terutama bagi orang yang tidak terlalu mahir dalam interaksi sosial. Karena sifatnya yang seperti bayangan, ia bisa memungkinkan perilaku yang lebih patologis, dan bahkan jika tidak menjadi sangat buruk pun, ia dapat membuat koordinasi jauh lebih sulit.
      Saya pernah mengalami hal semacam ini di beberapa perusahaan. Di sebuah perusahaan konsultan besar, memang ada struktur kekuasaan resmi, tetapi dalam praktiknya tidak terlalu diikuti, dan cara masuk ke proyek lebih mirip dengan menjadi akrab dengan orang-orang sales/manajemen daripada lewat kanal resmi. Jika Anda bisa membangun jejaring sosial yang dibutuhkan, itu bagus, tetapi jika tidak, sistemnya tidak berjalan baik.
      Contoh serupa adalah “The Tyranny of Structurelessness”. Itu adalah ceramah dari seorang feminis yang melihat hal yang sama terjadi di dalam organisasi yang menolak hierarki karena menganggapnya patriarkal, dan ada juga diskusi serupa tentang Valve yang struktur internalnya tidak jelas. Proyek open source juga bisa mengalami masalah yang sama, dan menurut saya sebagian konflik di Rust juga berakar pada masalah serupa.
      Meski begitu, struktur kekuasaan eksplisit tidak harus hierarkis. Organisasi bisnis tradisional memang hierarkis, tetapi struktur Oxide bisa saja eksplisit sekaligus non-hierarkis. Cara seperti ini biasanya bekerja lebih baik pada skala yang lebih kecil, dan perusahaan konsultan yang saya sebut tadi adalah contoh terbesar yang saya tahu dari perusahaan yang beroperasi cukup mendekati gaya bebas, tetapi tetap masih ada semacam pijakan pendukung.
      Ini bukan dikotomi, melainkan spektrum. Bahkan struktur kekuasaan yang paling kaku di atas kertas pun memiliki struktur implisit yang lebih kompleks di bawahnya, dan itu adalah sifat dasar kelompok manusia.
      Saya tidak percaya bahwa struktur eksplisit selalu lebih baik daripada struktur implisit. Saya hanya menyebutkan kekurangan yang diamati pada organisasi yang kurang eksplisit, dan struktur kekuasaan yang lebih eksplisit juga punya masalahnya sendiri. Terkait hal ini, ada juga isu “seeing like a state” atau legibility
  • Tulisan yang sangat bagus dan menunjukkan secara mendalam proses debugging masalah yang kompleks. Fakta bahwa bagian sistem lainnya tetap stabil dengan baik menunjukkan kualitas engineering tim Oxide.
    Secara pribadi saya juga cukup terinspirasi, dan berniat mencoba menerapkan teknik serupa dalam pekerjaan sehari-hari

  • Jika hardware itu diperlakukan seperti TLB yang diisi oleh software, mereka juga bisa mendukung lebih dari 8 region

    • Mungkin mereka (a) menginginkan performa soft real-time, dan (b) tidak ingin memasukkan komponen inti yang dapat mengganggu kemampuan debugging atau reliabilitas.
      Saya tidak akan melakukannya kecuali sebagai pilihan terakhir. Virtual paging itu berantakan, dan saya tidak ingin menyisakan keraguan
    • Saya tahu TLB berarti translation lookaside buffer, tetapi saya penasaran apa arti “soft fill” di sini
  • Apa yang dikerjakan Oxide benar-benar menakjubkan

    • Setelah Tailscale, sekarang Oxide menjadi proyek kesayangan yang tidak dibutuhkan oleh 99% orang
  • Saya suka apa pun yang dikerjakan orang-orang Oxide, dan ini salah satunya

  • Mereka menamai sistem operasinya Hubris? Ah, itu... saya sampai tak sanggup berkata-kata

    • Anda akan senang mengetahui bahwa nama debugger-nya adalah “humility”: https://github.com/oxidecomputer/humility
    • Tepatnya, Brian Cantrill-lah yang menamai sistem operasi itu hubris.
      Orang waras mana yang mau menulis sistem operasi baru di zaman sekarang? Jawabannya adalah orang yang ingin memecahkan masalah yang diabaikan semua sistem operasi: masalah controller pada motherboard dan kartu ekspansi yang tidak dikendalikan, dan memang tidak bisa dikendalikan, oleh sistem operasi
    • Tampaknya cukup cocok dengan brand mereka