149 lines
4.6 KiB
Go
149 lines
4.6 KiB
Go
package fuzzer
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"math/rand"
|
||
)
|
||
|
||
// mutateFunc — одна стратегия мутации: берёт данные, возвращает новую
|
||
// (не модифицирует исходный срез — воркеры работают конкурентно с общим
|
||
// корпусом, in-place мутация была бы гонкой данных).
|
||
type mutateFunc func(rng *rand.Rand, data []byte) []byte
|
||
|
||
var mutators = []mutateFunc{
|
||
mutateBitFlip,
|
||
mutateByteFlip,
|
||
mutateArith,
|
||
mutateInteresting,
|
||
mutateBlockDelete,
|
||
mutateBlockDuplicate,
|
||
}
|
||
|
||
func mutateBitFlip(rng *rand.Rand, data []byte) []byte {
|
||
if len(data) == 0 {
|
||
return data
|
||
}
|
||
out := append([]byte(nil), data...)
|
||
idx := rng.Intn(len(out))
|
||
bit := rng.Intn(8)
|
||
out[idx] ^= 1 << bit
|
||
return out
|
||
}
|
||
|
||
func mutateByteFlip(rng *rand.Rand, data []byte) []byte {
|
||
if len(data) == 0 {
|
||
return data
|
||
}
|
||
out := append([]byte(nil), data...)
|
||
out[rng.Intn(len(out))] = byte(rng.Intn(256))
|
||
return out
|
||
}
|
||
|
||
// interestingBytes/16/32 — классический AFL-набор "граничных" значений:
|
||
// нули, минус единица, границы знакового/беззнакового диапазона. Такие
|
||
// значения статистически чаще ломают проверки длины/индексов, чем
|
||
// случайный байт.
|
||
var (
|
||
interestingBytes = []byte{0x00, 0x01, 0x7f, 0x80, 0xff}
|
||
interesting16 = []int16{0, 1, -1, 0x7fff, -0x8000, 0xff, 0x100}
|
||
interesting32 = []int32{0, 1, -1, 0x7fffffff, -0x80000000, 0xffff, 0x10000}
|
||
)
|
||
|
||
func mutateInteresting(rng *rand.Rand, data []byte) []byte {
|
||
if len(data) == 0 {
|
||
return data
|
||
}
|
||
out := append([]byte(nil), data...)
|
||
switch rng.Intn(3) {
|
||
case 0:
|
||
out[rng.Intn(len(out))] = interestingBytes[rng.Intn(len(interestingBytes))]
|
||
case 1:
|
||
if len(out) >= 2 {
|
||
idx := rng.Intn(len(out) - 1)
|
||
v := interesting16[rng.Intn(len(interesting16))]
|
||
binary.LittleEndian.PutUint16(out[idx:], uint16(v))
|
||
}
|
||
default:
|
||
if len(out) >= 4 {
|
||
idx := rng.Intn(len(out) - 3)
|
||
v := interesting32[rng.Intn(len(interesting32))]
|
||
binary.LittleEndian.PutUint32(out[idx:], uint32(v))
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// mutateArith — прибавляет/отнимает небольшое значение (1..35, тот же
|
||
// диапазон, что ARITH_MAX в AFL) от случайного байта. Ловит off-by-one
|
||
// и похожие арифметические баги, которые чистый bit-flip обычно не находит.
|
||
func mutateArith(rng *rand.Rand, data []byte) []byte {
|
||
if len(data) == 0 {
|
||
return data
|
||
}
|
||
out := append([]byte(nil), data...)
|
||
idx := rng.Intn(len(out))
|
||
delta := byte(rng.Intn(35) + 1)
|
||
if rng.Intn(2) == 0 {
|
||
out[idx] += delta
|
||
} else {
|
||
out[idx] -= delta
|
||
}
|
||
return out
|
||
}
|
||
|
||
func mutateBlockDelete(rng *rand.Rand, data []byte) []byte {
|
||
if len(data) < 2 {
|
||
return data
|
||
}
|
||
start := rng.Intn(len(data))
|
||
length := 1 + rng.Intn(len(data)-start)
|
||
out := append([]byte(nil), data[:start]...)
|
||
out = append(out, data[start+length:]...)
|
||
return out
|
||
}
|
||
|
||
func mutateBlockDuplicate(rng *rand.Rand, data []byte) []byte {
|
||
if len(data) == 0 {
|
||
return data
|
||
}
|
||
start := rng.Intn(len(data))
|
||
length := 1 + rng.Intn(len(data)-start)
|
||
block := data[start : start+length]
|
||
insertAt := rng.Intn(len(data) + 1)
|
||
out := append([]byte(nil), data[:insertAt]...)
|
||
out = append(out, block...)
|
||
out = append(out, data[insertAt:]...)
|
||
return out
|
||
}
|
||
|
||
// splice скрещивает два входа в случайной точке — классическая техника
|
||
// "склеить два интересных сэмпла и посмотреть, что получится".
|
||
func splice(rng *rand.Rand, a, b []byte) []byte {
|
||
if len(a) == 0 || len(b) == 0 {
|
||
return append([]byte(nil), a...)
|
||
}
|
||
n := len(a)
|
||
if len(b) < n {
|
||
n = len(b)
|
||
}
|
||
cut := rng.Intn(n)
|
||
out := append([]byte(nil), a[:cut]...)
|
||
out = append(out, b[cut:]...)
|
||
return out
|
||
}
|
||
|
||
// havoc — стадия из классического AFL, которая на практике находит
|
||
// больше всего крашей: несколько случайных мутаций подряд за один проход,
|
||
// иногда вперемешку со splice по другому сэмплу из корпуса.
|
||
func havoc(rng *rand.Rand, data []byte, corpus [][]byte) []byte {
|
||
out := append([]byte(nil), data...)
|
||
rounds := 1 + rng.Intn(8)
|
||
for i := 0; i < rounds; i++ {
|
||
if len(corpus) > 1 && rng.Intn(4) == 0 {
|
||
out = splice(rng, out, corpus[rng.Intn(len(corpus))])
|
||
continue
|
||
}
|
||
out = mutators[rng.Intn(len(mutators))](rng, out)
|
||
}
|
||
return out
|
||
}
|