gin/fuzz_test.go
can olgun 8f0fdaca40 Add Go fuzz tests for OSS-Fuzz integration
Covers the core pre-auth request parsing boundaries in Gin:
- JSON request body binding (BindBody) - every JSON API
- URL path matching - router parameter extraction
- Form POST binding - form data parsing

Gin is the most popular Go web framework with 88K+ stars
and is imported by thousands of Go projects.
2026-06-12 04:35:22 +03:00

103 lines
2.5 KiB
Go

//go:build go1.18
// +build go1.18
// Copyright 2014 Manu Martinez-Almeida
// SPDX-License-Identifier: MIT
package gin_test
import (
"net/http"
"strings"
"testing"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)
// FuzzJSONBinding tests JSON request body binding with arbitrary
// attacker-controlled byte input.
//
// This is the pre-auth boundary for every JSON API built with Gin.
// Every JSON request body passes through this code path.
// Gin has 88K+ GitHub stars and is imported by thousands of Go projects.
//
// 5 GitHub Security Advisories exist for Gin.
func FuzzJSONBinding(f *testing.F) {
jsonBinding := binding.JSON
f.Add([]byte(`{"name":"test","age":30}`))
f.Add([]byte(`{}`))
f.Add([]byte(``))
f.Add([]byte(`{"a":`))
f.Add([]byte(`null`))
f.Add([]byte(`[1,2,3]`))
f.Add(make([]byte, 10000))
f.Fuzz(func(t *testing.T, body []byte) {
if len(body) > 1<<16 {
return
}
var obj map[string]any
// BindBody must never panic on any input
_ = jsonBinding.BindBody(body, &obj)
})
}
// FuzzGinPathMatch tests URL path parameter matching with
// arbitrary attacker-controlled paths.
//
// Path matching determines routing and extracts parameters
// from URLs. This is the first code that processes every
// incoming HTTP request.
func FuzzGinPathMatch(f *testing.F) {
f.Add("/users/:id", "/users/123")
f.Add("/api/:version/users/:id", "/api/v1/users/42")
f.Add("/", "/")
f.Add("/static/*filepath", "/static/css/main.css")
f.Add(strings.Repeat("/a", 100), "/a/a")
f.Fuzz(func(t *testing.T, pattern, path string) {
if len(pattern) > 10000 || len(path) > 10000 {
return
}
if pattern == "" {
return
}
// Test route construction + matching
router := gin.New()
func() {
defer func() { _ = recover() }()
router.GET(pattern, func(c *gin.Context) {})
router.Handle("GET", path, func(c *gin.Context) {})
}()
})
}
// FuzzFormBinding tests form POST body binding with arbitrary
// key-value pairs from HTTP POST requests.
func FuzzFormBinding(f *testing.F) {
f.Add("name=test&age=30")
f.Add("")
f.Add("a=1&b=2&c=3")
f.Fuzz(func(t *testing.T, formData string) {
if len(formData) > 1<<16 {
return
}
// Form binding parses POST form data
// Create a minimal request with form body
req, err := http.NewRequest("POST", "/", strings.NewReader(formData))
if err != nil {
return
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
var obj map[string]string
_ = binding.Form.Bind(req, &obj)
})
}