Skip to content

Commit

Permalink
fuzz: add fuzzer for ternary ops (mulmod/addmod) (#95)
Browse files Browse the repository at this point in the history
* fuzz: add fuzzer for mulmod

* tests: improve fuzzers to run all cases

* ci: reset cached fuzzing corpus

Co-authored-by: Paweł Bylica <chfast@gmail.com>
  • Loading branch information
holiman and chfast committed Aug 31, 2021
1 parent 833833c commit 71c9c37
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 48 deletions.
4 changes: 2 additions & 2 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
command: bash <(curl -s https://codecov.io/bash)
- restore_cache:
keys:
- corpus
- corpus-v2
- run:
name: "Fuzzing"
command: |
Expand All @@ -49,7 +49,7 @@ jobs:
timeout --preserve-status --signal INT 1m go-fuzz -procs=2
test ! "$(ls crashers)"
- save_cache:
key: corpus-{{ epoch }}
key: corpus-v2-{{ epoch }}
paths:
- corpus
- run:
Expand Down
201 changes: 155 additions & 46 deletions fuzz.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright 2020 uint256 Authors
// SPDX-License-Identifier: BSD-3-Clause

//go:build gofuzz
// +build gofuzz

package uint256
Expand All @@ -11,105 +12,180 @@ import (
"math/big"
"reflect"
"runtime"
"strings"
)

const (
opUdivrem = 0
opMul = 1
opLsh = 2
opAdd = 4
opSub = 5
opUdivrem = iota
opMul
opLsh
opAdd
opSub
opMulmod
)

type opFunc func(*Int, *Int, *Int) *Int
type bigFunc func(*big.Int, *big.Int, *big.Int) *big.Int
type opDualArgFunc func(*Int, *Int, *Int) *Int
type bigDualArgFunc func(*big.Int, *big.Int, *big.Int) *big.Int

func crash(op opFunc, x, y Int, msg string) {
type opThreeArgFunc func(*Int, *Int, *Int, *Int) *Int
type bigThreeArgFunc func(*big.Int, *big.Int, *big.Int, *big.Int) *big.Int

func crash(op interface{}, msg string, args ...Int) {
fn := runtime.FuncForPC(reflect.ValueOf(op).Pointer())
fnName := fn.Name()
fnFile, fnLine := fn.FileLine(fn.Entry())
panic(fmt.Sprintf("%s\nfor %s (%s:%d)\nx: %x\ny: %x", msg, fnName, fnFile, fnLine, &x, &y))
var strArgs []string
for i, arg := range args {
strArgs = append(strArgs, fmt.Sprintf("%d: %x", i, &arg))
}
panic(fmt.Sprintf("%s\nfor %s (%s:%d)\n%v",
msg, fnName, fnFile, fnLine, strings.Join(strArgs, "\n")))
}

func checkOp(op opFunc, bigOp bigFunc, x, y Int) {
func checkDualArgOp(op opDualArgFunc, bigOp bigDualArgFunc, x, y Int) {
origX := x
origY := y

var result Int
ret := op(&result, &x, &y)
if ret != &result {
crash(op, x, y, "returned not the pointer receiver")
crash(op, "returned not the pointer receiver", x, y)
}
if x != origX {
crash(op, x, y, "first argument modified")
crash(op, "first argument modified", x, y)
}
if y != origY {
crash(op, x, y, "second argument modified")
crash(op, "second argument modified", x, y)
}

expected, _ := FromBig(bigOp(new(big.Int), x.ToBig(), y.ToBig()))
if result != *expected {
crash(op, x, y, "unexpected result")
crash(op, "unexpected result", x, y)
}

// Test again when the receiver is not zero.
var garbage Int
garbage.Xor(&x, &y)
ret = op(&garbage, &x, &y)
if ret != &garbage {
crash(op, x, y, "returned not the pointer receiver")
crash(op, "returned not the pointer receiver", x, y)
}
if garbage != *expected {
crash(op, x, y, "unexpected result")
crash(op, "unexpected result", x, y)
}
if x != origX {
crash(op, x, y, "first argument modified")
crash(op, "first argument modified", x, y)
}
if y != origY {
crash(op, x, y, "second argument modified")
crash(op, "second argument modified", x, y)
}

// Test again with the receiver aliasing arguments.
ret = op(&x, &x, &y)
if ret != &x {
crash(op, x, y, "returned not the pointer receiver")
crash(op, "returned not the pointer receiver", x, y)
}
if x != *expected {
crash(op, x, y, "unexpected result")
crash(op, "unexpected result", x, y)
}

ret = op(&y, &origX, &y)
if ret != &y {
crash(op, x, y, "returned not the pointer receiver")
crash(op, "returned not the pointer receiver", x, y)
}
if y != *expected {
crash(op, x, y, "unexpected result")
crash(op, "unexpected result", x, y)
}
}

func Fuzz(data []byte) int {
if len(data) != 65 {
return 0
}
func checkThreeArgOp(op opThreeArgFunc, bigOp bigThreeArgFunc, x, y, z Int) {
origX := x
origY := y
origZ := z

op := data[0]
var result Int
ret := op(&result, &x, &y, &z)
if ret != &result {
crash(op, "returned not the pointer receiver", x, y, z)
}
switch {
case x != origX:
crash(op, "first argument modified", x, y, z)
case y != origY:
crash(op, "second argument modified", x, y, z)
case z != origZ:
crash(op, "third argument modified", x, y, z)
}
expected, _ := FromBig(bigOp(new(big.Int), x.ToBig(), y.ToBig(), z.ToBig()))
if have, want := result, *expected; have != want {
crash(op, fmt.Sprintf("unexpected result: have %v want %v", have, want), x, y, z)
}

var x, y Int
x.SetBytes(data[1:33])
y.SetBytes(data[33:])
// Test again when the receiver is not zero.
var garbage Int
garbage.Xor(&x, &y)
ret = op(&garbage, &x, &y, &z)
if ret != &garbage {
crash(op, "returned not the pointer receiver", x, y, z)
}
if have, want := garbage, *expected; have != want {
crash(op, fmt.Sprintf("unexpected result: have %v want %v", have, want), x, y, z)
}
switch {
case x != origX:
crash(op, "first argument modified", x, y, z)
case y != origY:
crash(op, "second argument modified", x, y, z)
case z != origZ:
crash(op, "third argument modified", x, y, z)
}

switch op {
case opUdivrem:
if y.IsZero() {
return 0
}
checkOp((*Int).Div, (*big.Int).Div, x, y)
checkOp((*Int).Mod, (*big.Int).Mod, x, y)
// Test again with the receiver aliasing arguments.
ret = op(&x, &x, &y, &z)
if ret != &x {
crash(op, "returned not the pointer receiver", x, y, z)
}
if have, want := x, *expected; have != want {
crash(op, fmt.Sprintf("unexpected result: have %v want %v", have, want), x, y, z)
}

case opMul:
checkOp((*Int).Mul, (*big.Int).Mul, x, y)
ret = op(&y, &origX, &y, &z)
if ret != &y {
crash(op, "returned not the pointer receiver", x, y, z)
}
if y != *expected {
crash(op, "unexpected result", x, y, z)
}
ret = op(&z, &origX, &origY, &z)
if ret != &z {
crash(op, "returned not the pointer receiver", x, y, z)
}
if z != *expected {
crash(op, fmt.Sprintf("unexpected result: have %v want %v", z.ToBig(), expected), x, y, z)
}
}

case opLsh:
func Fuzz(data []byte) int {
switch len(data) {
case 64:
return fuzzBinaryOp(data)
case 96:
return fuzzTernaryOp(data)
}
return -1
}
func fuzzBinaryOp(data []byte) int {
var x, y Int
x.SetBytes(data[0:32])
y.SetBytes(data[32:])
if !y.IsZero() { // uDivrem
checkDualArgOp((*Int).Div, (*big.Int).Div, x, y)
checkDualArgOp((*Int).Mod, (*big.Int).Mod, x, y)
}
{ // opMul
checkDualArgOp((*Int).Mul, (*big.Int).Mul, x, y)
}
{ // opLsh
lsh := func(z, x, y *Int) *Int {
return z.Lsh(x, uint(y[0]))
}
Expand All @@ -120,14 +196,47 @@ func Fuzz(data []byte) int {
}
return z.Lsh(x, n)
}
checkOp(lsh, bigLsh, x, y)
checkDualArgOp(lsh, bigLsh, x, y)
}
{ // opAdd
checkDualArgOp((*Int).Add, (*big.Int).Add, x, y)
}
{ // opSub
checkDualArgOp((*Int).Sub, (*big.Int).Sub, x, y)
}
return 1
}

case opAdd:
checkOp((*Int).Add, (*big.Int).Add, x, y)
func bigMulMod(b1, b2, b3, b4 *big.Int) *big.Int {
return b1.Mod(big.NewInt(0).Mul(b2, b3), b4)
}

case opSub:
checkOp((*Int).Sub, (*big.Int).Sub, x, y)
func intMulMod(f1, f2, f3, f4 *Int) *Int {
return f1.MulMod(f2, f3, f4)
}

func bigAddMod(b1, b2, b3, b4 *big.Int) *big.Int {
return b1.Mod(big.NewInt(0).Add(b2, b3), b4)
}

func intAddMod(f1, f2, f3, f4 *Int) *Int {
return f1.AddMod(f2, f3, f4)
}

func fuzzTernaryOp(data []byte) int {
var x, y, z Int
x.SetBytes(data[:32])
y.SetBytes(data[32:64])
z.SetBytes(data[64:])
if z.IsZero() {
return 0
}

return 0
{ // mulMod
checkThreeArgOp(intMulMod, bigMulMod, x, y, z)
}
{ // addMod
checkThreeArgOp(intAddMod, bigAddMod, x, y, z)
}
return 1
}

0 comments on commit 71c9c37

Please sign in to comment.