Giới Thiệu
Trong công việc lập trình, các lập trình viên thường không thể dự đoán hết các đầu vào mà chương trình hoặc hàm của họ có thể nhận được. Mặc dù họ có thể xác định được một số trường hợp đặc biệt, nhưng không có gì đảm bảo rằng những tình huống bất ngờ sẽ không xuất hiện. Chính vì lý do này, việc phát hiện lỗi thường chỉ dừng lại ở những gì lập trình viên dự đoán và mong đợi.
Để vượt qua thách thức này, phương pháp kiểm thử mờ (fuzzing) đã ra đời. Trong bài viết này, chúng ta sẽ khám phá cách thực hiện kiểm thử mờ trong Golang để phát hiện những lỗi ẩn.
Fuzz Testing Là Gì?
Fuzzing là một kỹ thuật kiểm thử phần mềm tự động, cho phép bạn gửi một khối lượng lớn dữ liệu ngẫu nhiên hợp lệ, gần như hợp lệ hoặc không hợp lệ đến một chương trình và theo dõi hành vi của nó. Mục tiêu của fuzzing là làm rõ lỗi, sự cố và lỗ hổng an ninh trong mã mà bạn não chưa thể nhận ra bằng các phương pháp kiểm thử truyền thống.
Ví dụ về một mã Golang có thể bị lỗi:
go
func Equal(a []byte, b []byte) bool {
for i := range a {
// có thể xảy ra panic với lỗi runtime: chỉ mục ngoài phạm vi.
if a[i] != b[i] {
return false
}
}
return true
}
Hàm trên hoạt động tốt miễn là độ dài của hai slice bằng nhau, nhưng nó sẽ gặp lỗi khi slice đầu tiên dài hơn slice thứ hai.
Tích Hợp Fuzzing Trong Quy Trình Phát Triển
Việc tích hợp fuzzing vào quá trình phát triển phần mềm (SDLC) là một thực hành tốt. Microsoft, ví dụ, sử dụng fuzzing như một bước trong SDLC để tìm ra các lỗi và lỗ hổng tiềm ẩn.
Các Bước Thực Hiện Kiểm Thử Mờ Trong Golang
Với Go 1.18, fuzzing đã trở thành một phần của thư viện chuẩn, giúp việc thực hiện fuzzing trở nên dễ dàng hơn. Dưới đây là các bước để tạo fuzz test trong Golang:
- Tạo một hàm mới trong tệp
*_test.go
bắt đầu bằng từ khóaFuzz
, nhận đối số là*testing.F
. - Thêm các hạt giống corpus bằng cách sử dụng
f.Add()
để fuzzer có thể tạo dữ liệu từ đó. - Gọi hàm fuzz target bằng
f.Fuzz()
và truyền vào các đối số mà hàm của bạn chấp nhận. - Khởi động fuzzing bằng lệnh
go test
, kèm theo cờ–fuzz=Fuzz
.
Ví Dụ Về Fuzz Test Đơn Giản
go
// Fuzz test
func FuzzEqual(f *testing.F) {
f.Add([]byte{'f', 'u', 'z', 'z'}, []byte{'t', 'e', 's', 't'})
f.Fuzz(func(t *testing.T, a []byte, b []byte) {
Equal(a, b)
})
}
Khi chạy lệnh fuzz, nếu có lỗi xảy ra, thông tin sẽ được ghi lại trong thư mục testdata
, cho phép bạn xem lại và tái hiện đầu vào gây ra lỗi đó.
Fuzzing Dịch Vụ HTTP Trong Golang
Chúng ta cũng có thể thực hiện fuzz testing cho các dịch vụ HTTP bằng cách sử dụng httptest
từ Golang. Một ví dụ thực tế là xây dựng một HTTP handler chấp nhận một yêu cầu JSON với các trường limit
và offset
.
Định Nghĩa Cấu Trúc Yêu Cầu
go
type Request struct {
Limit int `json:"limit"`
Offset int `json:"offset"`
}
Triển Khai Hàm Xử Lý
go
func ProcessRequest(w http.ResponseWriter, r *http.Request) {
// ... (mã chi tiết xử lý yêu cầu)
}
Hàm này có thể gặp lỗi nếu người dùng nhập vào các giá trị không hợp lệ. Việc thực hiện fuzzing trong hàm xử lý này sẽ giúp phát hiện những vấn đề này.
Triển Khai Fuzz Test Cho HTTP Handler
go
func FuzzProcessRequest(f *testing.F) {
// ... (chuẩn bị fuzz test)
}
Chạy Fuzz Testing
Khi fuzzing các dịch vụ HTTP, chúng ta cần chú ý điều chỉnh số lượng worker song song để tránh quá tải máy chủ kiểm thử. Ví dụ:
go test --fuzz=Fuzz -fuzztime=10s -parallel=1
Kết Luận
Việc viết fuzz test cho bất kỳ dịch vụ nào trong ứng dụng của bạn là một cách hiệu quả để phát hiện lỗi. Fuzzers giúp tiết lộ những lỗi tiềm ẩn chỉ xuất hiện với đầu vào bất ngờ. Chúng tích hợp tốt với các bài kiểm tra thông thường trong Golang, đóng góp vào việc cải thiện chất lượng mã nguồn.
Với những kiến thức này, bạn có thể bắt đầu triển khai fuzz testing cho các ứng dụng Golang của mình ngay hôm nay.