Skip to content

Commit

Permalink
Integrate with Fuzzit (tsenart#432)
Browse files Browse the repository at this point in the history
Enables automated fuzzing on continuous fuzzing platform Fuzzit.
Fuzz regression tests run every build and PR. Full length
fuzzing runs every push to master.

Targets everything that does some parsing.

Defined fuzzing targets are:
* AttackerHTTP - Delivers fuzz as an HTTP response. Uses socket file.
* AttackerTCP - Delivers fuzz as a TCP byte stream. Uses socket file.
* HTTPTargeter - Fuzz decoding of a target list in HTTP format.
* JSONTargeter - Fuzz decoding of a target list in JSON format.
* ResultsFormatDetection - Fuzz result list format detection.
* GobDecoder - Fuzz decoder of a result list in gob format.
* CSVDecoder - Fuzz decoding of a result list in CSV format.
* JSONDecoder - Fuzz decoding a result list in JSON format.
  • Loading branch information
bookmoons authored and tsenart committed Sep 4, 2019
1 parent b5f4fca commit 0dfc10f
Show file tree
Hide file tree
Showing 7 changed files with 464 additions and 5 deletions.
22 changes: 18 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
language: go
services:
- docker
sudo: false
go:
- tip
install:
- go get -v golang.org/x/lint/golint
- go get -d -t -v ./...
- go build -v ./...
script:
- go vet ./...
- $HOME/gopath/bin/golint -set_exit_status $(go list ./... | grep -v /vendor/)
- go test -v ./...
jobs:
include:
- stage: Test
script:
- go vet ./...
- $HOME/gopath/bin/golint -set_exit_status $(go list ./... | grep -v /vendor/)
- go test -v ./...
- stage: Fuzz regression
go: 1.12.x
dist: bionic
script: ./fuzzit.sh local-regression
- stage: Fuzz
if: branch = master AND type IN (push)
go: 1.12.x
dist: bionic
script: ./fuzzit.sh fuzzing
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Vegeta [![Build Status](https://secure.travis-ci.org/tsenart/vegeta.svg?branch=master)](http://travis-ci.org/tsenart/vegeta) [![Go Report Card](https://goreportcard.com/badge/github.com/tsenart/vegeta)](https://goreportcard.com/report/github.com/tsenart/vegeta) [![GoDoc](https://godoc.org/github.com/tsenart/vegeta?status.svg)](https://godoc.org/github.com/tsenart/vegeta) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tsenart/vegeta?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Donate](https://img.shields.io/badge/donate-bitcoin-yellow.svg)](#donate)
# Vegeta [![Build Status](https://secure.travis-ci.org/tsenart/vegeta.svg?branch=master)](http://travis-ci.org/tsenart/vegeta) [![Fuzzit Status](https://app.fuzzit.dev/badge?org_id=vegeta)](https://app.fuzzit.dev/orgs/vegeta/dashboard) [![Go Report Card](https://goreportcard.com/badge/github.com/tsenart/vegeta)](https://goreportcard.com/report/github.com/tsenart/vegeta) [![GoDoc](https://godoc.org/github.com/tsenart/vegeta?status.svg)](https://godoc.org/github.com/tsenart/vegeta) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tsenart/vegeta?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Donate](https://img.shields.io/badge/donate-bitcoin-yellow.svg)](#donate)

Vegeta is a versatile HTTP load testing tool built out of a need to drill
HTTP services with a constant request rate.
Expand Down
40 changes: 40 additions & 0 deletions fuzzit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/bash
set -xe

# Validate arguments
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <fuzz-type>"
exit 1
fi

# Configure
NAME=vegeta
ROOT=./lib
TYPE=$1

# Setup
export GO111MODULE="off"
go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build
go get -d -v -u ./...
if [ ! -f fuzzit ]; then
wget -q -O fuzzit https://github.com/fuzzitdev/fuzzit/releases/download/v2.4.29/fuzzit_Linux_x86_64
chmod a+x fuzzit
fi

# Fuzz
function fuzz {
FUNC=Fuzz$1
TARGET=$2
DIR=${3:-$ROOT}
go-fuzz-build -libfuzzer -func $FUNC -o fuzzer.a $DIR
clang -fsanitize=fuzzer fuzzer.a -o fuzzer
./fuzzit create job --type $TYPE $NAME/$TARGET fuzzer
}
fuzz HTTPTargeter http-targeter
fuzz JSONTargeter json-targeter
fuzz ResultsFormatDetection results-format-detection
fuzz GobDecoder gob-decoder
fuzz CSVDecoder csv-decoder
fuzz JSONDecoder json-decoder
fuzz AttackerTCP attacker-tcp
fuzz AttackerHTTP attacker-http
154 changes: 154 additions & 0 deletions lib/attack_fuzz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// +build gofuzz

package vegeta

import (
"encoding/binary"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"time"
)

// FuzzAttackerTCP fuzzes binary responses to attacker.
func FuzzAttackerTCP(fuzz []byte) int {
// Ignore empty fuzz
if len(fuzz) == 0 {
return -1
}

// Start server
directory, err := ioutil.TempDir("/tmp", "fuzz")
if err != nil {
panic(err.Error())
}
socket := fmt.Sprintf("%s/attacker.sock", directory)
listener, err := net.Listen("unix", socket)
if err != nil {
panic(err.Error())
}
go func() {
connection, err := listener.Accept()
if err != nil {
panic(err.Error())
}
_, err = connection.Write(fuzz)
if err != nil {
panic(err.Error())
}
err = connection.Close()
if err != nil {
panic(err.Error())
}
}()
defer listener.Close()
defer os.RemoveAll(directory)

// Setup targeter
targeter := Targeter(func(target *Target) error {
target.Method = "GET"
target.URL = "http://vegeta.test"
return nil
})

// Deliver a single hit
attacker := NewAttacker(
UnixSocket(socket),
Workers(1),
MaxWorkers(1),
Timeout(time.Second),
KeepAlive(false),
)
result := attacker.hit(targeter, "fuzz")
if result.Error != "" {
return 0
}
return 1
}

// FuzzAttackerHTTP fuzzes valid HTTP responses to attacker.
func FuzzAttackerHTTP(fuzz []byte) int {
// Decode response
code, headers, body, ok := decodeFuzzResponse(fuzz)
if !ok {
return -1
}

// Start server
directory, err := ioutil.TempDir("/tmp", "fuzz")
if err != nil {
panic(err.Error())
}
socket := fmt.Sprintf("%s/attacker.sock", directory)
listener, err := net.Listen("unix", socket)
if err != nil {
panic(err.Error())
}
handler := func(response http.ResponseWriter, request *http.Request) {
for name, values := range headers {
for _, value := range values {
response.Header().Add(name, value)
}
}
response.WriteHeader(int(code))
_, err := response.Write(body)
if err != nil {
panic(err.Error())
}
}
server := http.Server{
Handler: http.HandlerFunc(handler),
}
defer server.Close()
defer listener.Close()
defer os.RemoveAll(directory)
go server.Serve(listener)

// Setup targeter
targeter := Targeter(func(target *Target) error {
target.Method = "GET"
target.URL = "http://vegeta.test"
return nil
})

// Deliver a single hit
attacker := NewAttacker(
UnixSocket(socket),
Workers(1),
MaxWorkers(1),
Timeout(time.Second),
KeepAlive(false),
)
result := attacker.hit(targeter, "fuzz")
if result.Error != "" {
return 0
}
return 1
}

func decodeFuzzResponse(fuzz []byte) (
code int,
headers map[string][]string,
body []byte,
ok bool,
) {
if len(fuzz) < 2 {
return
}
headers = make(map[string][]string)
body = []byte{}
code = int(binary.LittleEndian.Uint16(fuzz[0:2]))
if len(fuzz) == 2 {
ok = true
return
}
fuzz, ok = decodeFuzzHeaders(fuzz[2:], headers)
if !ok {
return
}
body = fuzz
ok = true
return
}
63 changes: 63 additions & 0 deletions lib/results_fuzz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// +build gofuzz

package vegeta

import (
"bytes"
"io"
)

// FuzzResultsFormatDetection tests result list format detection.
func FuzzResultsFormatDetection(fuzz []byte) int {
decoder := DecoderFor(bytes.NewReader(fuzz))
if decoder == nil {
return 0
}
ok := readAllResults(decoder)
if !ok {
return 0
}
return 1
}

// FuzzGobDecoder tests decoding a gob format result list.
func FuzzGobDecoder(fuzz []byte) int {
decoder := NewDecoder(bytes.NewReader(fuzz))
ok := readAllResults(decoder)
if !ok {
return 0
}
return 1
}

// FuzzCSVDecoder tests decoding a CSV format result list.
func FuzzCSVDecoder(fuzz []byte) int {
decoder := NewCSVDecoder(bytes.NewReader(fuzz))
ok := readAllResults(decoder)
if !ok {
return 0
}
return 1
}

// FuzzJSONDecoder tests decoding a JSON format result list.
func FuzzJSONDecoder(fuzz []byte) int {
decoder := NewJSONDecoder(bytes.NewReader(fuzz))
ok := readAllResults(decoder)
if !ok {
return 0
}
return 1
}

func readAllResults(decoder Decoder) (ok bool) {
for {
result := &Result{}
err := decoder.Decode(result)
if err == io.EOF {
return true
} else if err != nil {
return false
}
}
}
68 changes: 68 additions & 0 deletions lib/targets_fuzz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// +build gofuzz

package vegeta

import (
"bytes"
"net/http"
)

// FuzzHTTPTargeter tests decoding an HTTP encoded target list.
func FuzzHTTPTargeter(fuzz []byte) int {
headers, body, fuzz, ok := decodeFuzzTargetDefaults(fuzz)
if !ok {
return -1
}
targeter := NewHTTPTargeter(
bytes.NewReader(fuzz),
body,
headers,
)
_, err := ReadAllTargets(targeter)
if err != nil {
return 0
}
return 1
}

// FuzzJSONTargeter tests decoding a JSON encoded target list.
func FuzzJSONTargeter(fuzz []byte) int {
headers, body, fuzz, ok := decodeFuzzTargetDefaults(fuzz)
if !ok {
return -1
}
targeter := NewJSONTargeter(
bytes.NewReader(fuzz),
body,
headers,
)
_, err := ReadAllTargets(targeter)
if err != nil {
return 0
}
return 1
}

func decodeFuzzTargetDefaults(fuzz []byte) (
headers http.Header,
body []byte,
rest []byte,
ok bool,
) {
if len(fuzz) < 2 {
return
}
headers = make(map[string][]string)
body = []byte{}
rest = []byte{}
rest, ok = decodeFuzzHeaders(fuzz, headers)
if !ok {
return
}
if len(rest) == 0 {
ok = true
return
}
body, rest, ok = extractFuzzByteString(rest)
return
}
Loading

0 comments on commit 0dfc10f

Please sign in to comment.