Go mendukung fuzzing pada perkakas baku sejak Go version 1.18. Pengujian fuzz pada Go didukung oleh OSS-Fuzz.
Cobalah tutorial untuk fuzzing pada Go.
Pendahuluan
Fuzzing adalah tipe pengujian otomatis yang secara terus menerus memanipulasi input-input pada program untuk menemukan kecacatan. Fuzzing pada Go menggunakan panduan cakupan (coverage guidance) untuk secara pintar menelusuri kode yang tengah di-fuzz and melaporkan kegagalan pada pengguna. Secara ia dapat menemukan kasus-kasus yang terkadang tidak terpikirkan oleh manusia, pengujian fuzz dapat berguna untuk menemukan celah keamanan yang dapat di-eksploitasi.
Contoh berikut adalah sebuah fuzz tes, menyoroti komponen-komponen utamanya.
Membuat fuzz tes
Kebutuhan
Berikut aturan-aturan yang harus diikuti oleh fuzz tes.
-
Sebuah fuzz tes haruslah sebuah fungsi dengan nama seperti
FuzzXxx, yang menerima hanya*testing.F, dan tidak memiliki kembalian. -
Fuzz tes haruslah berada dalam berkas `_test.go` supaya dapat dijalankan.
-
Sebuah target fuzz haruslah berupa pemanggilan method
*testing.Fyang menerima sebuah argumen*testing.Tsebagai parameter pertama, diikuti oleh argumen-argumen fuzzing. Tidak ada nilai kembalian. -
Harus ada satu target fuzz per satu fuzz tes.
-
Semua isi bibit corpus haruslah memiliki tipe yang identik dengan argumen fuzzing, dengan urutan yang sama. Hal ini juga berlaku untuk pemanggilan
(*testing.F).Adddan semua berkas corpus dalam direktoritestdata/fuzzdari fuzz tes. -
Argumen fuzzing hanya dapat memiliki tipe-tipe berikut:
-
string,[]byte -
int,int8,int16,int32/rune,int64 -
uint,uint8/byte,uint16,uint32,uint64 -
float32,float64 -
bool
-
Saran-saran
Berikut beberapa saran yang membantu Anda dalam fuzzing.
-
Target dari fuzz haruslah cepat dan deterministik sehingga mesin fuzzing dapat bekerja secara efisien, sehingga kegagalan yang ditemukan berikut cakupan kode dapat dengan mudah diulang.
-
Secara target fuzz dipanggil dengan paralel antara beberapa worker dan dengan urutan yang tidak deterministik, kondisi dari target fuzz sebaiknya tidak disimpan sampai pemanggilan berakhir, dan perilaku dari target fuzz sebaiknya tidak bergantung pada kondisi global.
Menjalankan fuzz tes
Ada dua mode untuk menjalankan fuzz tes: sebagai unit tes (dengan go
test), atau dengan fuzzing (go test -fuzz=FuzzTestName).
Fuzz tes berjalan seperti unit tes. Setiap isi bibit corpus akan diuji terhadap target fuzz, dan akan melaporkan bila ada kegagalan sebelum tes selesai.
Untuk mengaktifkan fuzzing, jalankan go test dengan opsi -fuzz, dengan
mengirim sebuah regex dengan nama fuzz tes yang diinginkan.
Secara bawaan, semua tes di dalam paket tersebut akan dijalankan sebelum
fuzz tes berjalan.
Hal ini untuk memastikan fuzz tes tidak melaporkan isu-isu yang mungkin
ditemukan oleh unit tes yang telah ada.
Ingatlah bahwa lamanya fuzz tes berjalan dapat ditentukan sendiri. Fuzz tes dapat berjalan selamanya jika tidak menemukan kesalahan. Nantinya akan ada dukungan untuk menjalankan fuzz tes menggunakan perkakas seperti OSS-Fuzz, lihat isu #50192.
|
Note
|
Fuzzing harus berjalan pada sistem yang mendukung instrumentasi cakupan (saat ini AMD64 dan ARM64) supaya corpus dapat terus berkembang saat fuzz tes berjalan, dan lebih banyak kode yang tercakup saat fuzzing. |
Keluaran perintah fuzzing
Saat fuzzing berjalan, mesin fuzzing menghasilkan input-input yang baru dan mengirimnya ke target fuzz. Secara bawaan, fuzz tes akan terus berjalan sampai menemukan input yang gagal, atau bila user membatalkan tes (misalnya dengan CTRL^C).
Keluaran dari perintah fuzz tes seperti berikut:
$ go test -fuzz FuzzFoo fuzz: elapsed: 0s, gathering baseline coverage: 0/192 completed fuzz: elapsed: 0s, gathering baseline coverage: 192/192 completed, now fuzzing with 8 workers fuzz: elapsed: 3s, execs: 325017 (108336/sec), new interesting: 11 (total: 202) fuzz: elapsed: 6s, execs: 680218 (118402/sec), new interesting: 12 (total: 203) fuzz: elapsed: 9s, execs: 1039901 (119895/sec), new interesting: 19 (total: 210) fuzz: elapsed: 12s, execs: 1386684 (115594/sec), new interesting: 21 (total: 212) PASS ok foo 12.692s
Baris pertama mengindikasikan bahwa "baseline coverage" (dasar cakupan) dikumpulkan sebelum fuzzing dimulai.
Untuk mengumpulkan dasar cakupan, mesin fuzzing mengeksekusi bibit corpus dan bangkitan corpus, untuk memastikan bahwa tidak ada kesalahan yang terjadi dan untuk mempelajari cakupan kode yang telah ditangani oleh corpus yang sudah ada.
Baris-baris selanjutnya menjelaskan eksekusi fuzzing:
-
elapsed: jumlah waktu yang telah berjalan sejak proses dimulai.
-
execs: jumlah input yang telah dikirim ke target fuzz (dengan rata-rata eksekusi/detik sejak baris sebelumnya)
-
new interesting: jumlah input yang "menarik" yang telah ditambahkan ke dalam bangkitan corpus selama eksekusi fuzzing (dengan total jumlah corpus).
Untuk sebuah input disebut "menarik", ia harus menambah cakupan kode melewati jumlah yang dihasilkan oleh corpus sebelumnya. Sangat wajar bila jumlah input "menarik" tersebut meningkat dengan cepat pada saat awal dan kemudian melambat, dengan sekali-kali meningkat saat cabang kode yang baru ditemukan.
Anda akan melihat jumlah "new interesting" semakin lama semakin naik saat input-input di dalam corpus menemukan baris-baris kode yang baru, yang terkadang-kadang melonjak saat mesin fuzzing menemukan jalur kode yang baru.
Input yang gagal
Sebuah kegagalan bisa terjadi saat fuzzing berjalan karena beberapa hal:
-
panic terjadi pada kode atau tes.
-
Target fuzz memanggil
t.Fail, baik secara langsung atau lewat method sepertit.Errorataut.Fatal. -
Kegagalan yang tidak diharapkan terjadi, seperti
os.Exitatau stack overflow. -
Target fuzz butuh waktu lama untuk selesai. Saat ini, tenggat waktu untuk sebuah eksekusi target fuzz yaitu 1 detik. Target fuzz bisa gagal disebabkan karena deadlock atau pengulangan tanpa henti, atau dari kondisi yang tidak diharapkan dalam kode. Karena inilah kenapa target fuzz disarankan harus cepat.
Bila sebuah kegagalan terjadi, mesin fuzzing akan mencoba me-minimalisasi ukuran input dengan nilai yang masih bisa dibaca oleh manusia, namun masih tetap menimbulkan kegagalan. Untuk mengatur hal ini, lihat bagian pengaturan khusus.
Setelah minimalisasi selesai, pesan kegagalan akan ditulis, dan menampilkan keluaran seperti berikut:
Failing input written to testdata/fuzz/FuzzFoo/a878c3134fe0404d44eb1e662e5d8d4a24beb05c3d68354903670ff65513ff49
To re-run:
go test -run=FuzzFoo/a878c3134fe0404d44eb1e662e5d8d4a24beb05c3d68354903670ff65513ff49
FAIL
exit status 1
FAIL foo 0.839s
Mesin fuzzing menulis
input yang gagal
ke bibit corpus dari fuzz tes tersebut, dan nanti akan dijalankan secara
otomatis lewat go test, yang dipakai sebagai regresi tes saat kecacatan
tersebut telah diperbaiki.
Langkah selanjutnya yaitu mendiagnosis permasalahan, memperbaiki kecacatan,
memverifikasi perbaikan dengan menjalankan go test kembali.
Pengaturan khusus
Pengaturan bawaan dari perintah go berjalan untuk kebanyakan kasus
dari fuzzing.
Pada umumnya, eksekusi fuzzing berbentuk seperti berikut:
$ go test -fuzz={FuzzTestName}
Namun, perintah go menyediakan beberapa pengaturan untuk menjalankan
fuzzing.
Ini didokumentasikan dalam
dokumentasi paket cmd/go.
Beberapa pengaturan tersebut antara lain:
-
-fuzztime: jumlah waktu atau jumlah iterasi yang akan dieksekusi oleh target fuzz sebelum keluar, nilai bakunya adalah selamanya. -
-fuzzminimizetime: waktu atau jumlah iterasi yang akan dieksekusi target fuzz selama minimalisasi, nilai bakunya 60 detik. Minimalisasi dapat dimatikan dengan-fuzzminimizetime 0. -
-parallel: jumlah proses fuzzing yang berjalan, nilai bakunya yaitu sama dengan nilai$GOMAXPROCS. Saat ini, opsi-cpusaat fuzzing tidak berpengaruh.
Format berkas corpus
Berkas-berkas corpus disimpan dengan format khusus. Format ini sama untuk bibit corpus, dan bangkitan corpus.
Berikut contoh berkas corpus:
go test fuzz v1
[]byte("hello\\xbd\\xb2=\\xbc ⌘")
int64(572293)
Baris pertama memberi tahu mesin fuzzing versi berkas corpus. Walaupun belum ada rencana ke depan untuk versi berkas terbaru, mesin fuzzing telah dirancang untuk mendukung hal tersebut.
Baris-baris selanjutnya adalah nilai-nilai dari corpus, yang dapat langsung disalin ke kode Go bila diinginkan.
Pada contoh di atas, nilai corpus adalah sebuah []byte diikuti oleh
sebuah int64.
Tipe-tipe ini harus sesuai dengan argumen fuzzing, secara berurutan.
Bentuk target fuzz dari tipe-tipe tersebut akan seperti berikut:
f.Fuzz(func(*testing.T, []byte, int64) {})
Cara termudah untuk menambahkan nilai bibit corpus secara manual yaitu
dengan menggunakan method (*testing.F).Add.
Pada contoh di atas, caranya seperti berikut:
f.Add([]byte("hello\\xbd\\xb2=\\xbc ⌘"), int64(572293))
Bila Anda memiliki berkas binari yang berukuran besar yang tidak
ingin disalin sebagai kode ke dalam tes, namun ingin digunakan sebagai isi
bibit corpus dalam direktori testdata/fuzz/{FuzzTestName}.
Perkakas
file2fuzz
yang ada di dalam
golang.org/x/tools/cmd/file2fuzz
dapat digunakan untuk mengonversi berkas binari tersebut menjadi berkas
corpus yang disimpan menjadi []byte.
Untuk menggunakan perkakas ini:
$ go install golang.org/x/tools/cmd/file2fuzz@latest $ file2fuzz -h
Sumber terkait
-
Tutorial
-
Cobalah tutorial Go fuzzing untuk mendalami konsep fuzzing.
-
Untuk yang lebih singkat, tutorial perkenalan fuzzing pada Go, silahkan lihat blog.
-
-
Dokumentasi
-
Detil teknis
Glosarium
argumen fuzzing: Tipe-tipe yang akan dikirim ke target fuzz, dan dimutasi oleh mutator.
bangkitan corpus: Sebuah corpus yang diatur oleh mesin fuzzing saat
fuzzing berjalan untuk mencatat progres.
Ia disimpan dalam $GOCACHE/fuzz.
Isi dari bangkitan corpus ini hanya digunakan saat fuzzing.
berkas tes: Sebuah berkas dengan format xxx_test.go yang bisa berisi
tes-test, benchmark, contoh-contoh kode dan fuzz tes.
bibit corpus: Corpus yang disediakan oleh pengembang untuk sebuah
fuzz tes yang dapat digunakan sebagai panduan bagi mesin fuzzing.
Isi corpus dibentuk dari pemanggilan
f.add di dalam fuzz tes, dan dari berkas-berkas di dalam direktori
testdata/fuzz/{FuzzTestName} dari paket yang diuji.
Isi-isi bibit corpus ini dijalankan oleh go test baik bila dijalankan
dengan opsi fuzzing atau tidak.
celah keamanan: Kelemahan keamanan dalam kode yang dapat di-eksploitasi oleh peretas.
fuzz tes: Sebuah fungsi dalam berkas tes dengan bentuk func
FuzzXxx(*testing.F) yang dapat digunakan untuk fuzzing.
fuzzing: Sebuah tipe dari pengujian otomatis yang secara terus menerus memanipulasi input pada sebuah program untuk menemukan kesalahan seperti bug atau celah keamanan pada kode.
input gagal: Sebuah input yang gagal yaitu isi corpus yang akan menyebabkan sebuah kegagalan atau panic saat dijalankan terhadap target fuzz.
isi corpus: Sebuah input dalam corpus yang dapat digunakan saat
fuzzing.
Isi corpus bisa berupa berkas dengan format khusus, atau pemanggilan
ke
(*testing.F).Add
mesin fuzzing: Sebuah perkakas yang mengatur fuzzing, termasuk menjaga corpus, memanggil mutator, meng-identifikasi cakupan yang baru, dan melaporkan kegagalan.
mutator: Sebuah perkakas yang digunakan saat fuzzing yang secara acak memanipulasi isi corpus sebelum mengirimnya ke target fuzz.
paket: Kumpulan berkas sumber kode dalam direktori yang sama yang dikompilasi bersamaan. Lihat bab Paket dalam Spesifikasi Bahasa Go.
panduan cakupan: Sebuah metoda fuzzing yang menggunakan ekspansi dalam cakupan kode untuk menentukan isi corpus mana yang pantas disimpan untuk penggunaan dimasa depan.
target fuzz: Fungsi dari fuzz tes yang dieksekusi dengan isi corpus dan
menghasilkan nilai saat fuzzing berjalan.
Sebuah target fuzz diberikan pada fuzz tes dengan mengirim fungsi tersebut
ke
(*testing.F).Fuzz.
Umpan balik
Jika Anda mengalami masalah atau memiliki ide untuk sebuah fitur, silahkan kirim isu.
Untuk diskusi dan umpan balik umum tentang fitur, Anda juga dapat berpartisipasi dalam kanal #fuzzing di Gophers Slack.