syzkaller/prog/hints_test.go
Dmitry Vyukov 9fe4bdc5f1 executor: overhaul
Make as much code as possible shared between all OSes.
In particular main is now common across all OSes.
Make more code shared between executor and csource
(in particular, loop function and threaded execution logic).
Also make loop and threaded logic shared across all OSes.
Make more posix/unix code shared across OSes
(e.g. signal handling, pthread creation, etc).
Plus other changes along similar lines.
Also support test OS in executor (based on portable posix)
and add 4 arches that cover all execution modes
(fork server/no fork server, shmem/no shmem).

This change paves way for testing of executor code
and allows to preserve consistency across OSes and executor/csource.
2018-07-24 12:04:27 +02:00

508 lines
14 KiB
Go

// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package prog
import (
"encoding/hex"
"fmt"
"math/rand"
"reflect"
"sort"
"testing"
)
type ConstArgTest struct {
name string
in uint64
comps CompMap
res uint64Set
}
type DataArgTest struct {
name string
in string
comps CompMap
res map[string]bool
}
// Tests checkConstArg(). Is not intended to check correctness of any mutations.
// Mutation are checked in their own tests.
func TestHintsCheckConstArg(t *testing.T) {
t.Parallel()
var tests = []ConstArgTest{
{
"One replacer test",
0xdeadbeef,
CompMap{0xdeadbeef: uint64Set{0xcafebabe: true}},
uint64Set{0xcafebabe: true},
},
// Test for cases when there's multiple comparisons (op1, op2), (op1, op3), ...
// Checks that for every such operand a program is generated.
{
"Multiple replacers test",
0xabcd,
CompMap{0xabcd: uint64Set{0x2: true, 0x3: true}},
uint64Set{0x2: true, 0x3: true},
},
// Checks that special ints are not used.
{
"Special ints test",
0xabcd,
CompMap{0xabcd: uint64Set{0x1: true, 0x2: true}},
uint64Set{0x2: true},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
t.Parallel()
res := uint64Set{}
constArg := &ConstArg{ArgCommon{nil}, test.in}
checkConstArg(constArg, test.comps, func() {
res[constArg.Val] = true
})
if !reflect.DeepEqual(res, test.res) {
t.Fatalf("\ngot : %v\nwant: %v", res, test.res)
}
})
}
}
// Tests checkDataArg(). Is not intended to check correctness of any mutations.
// Mutation are checked in their own tests.
func TestHintsCheckDataArg(t *testing.T) {
t.Parallel()
// All inputs are in Little-Endian.
var tests = []DataArgTest{
{
"One replacer test",
"\xef\xbe\xad\xde",
CompMap{0xdeadbeef: uint64Set{0xcafebabe: true}},
map[string]bool{
"\xbe\xba\xfe\xca": true,
},
},
// Test for cases when there's multiple comparisons (op1, op2), (op1, op3), ...
// Checks that for every such operand a program is generated.
{
"Multiple replacers test",
"\xcd\xab",
CompMap{0xabcd: uint64Set{0x2: true, 0x3: true}},
map[string]bool{
"\x02\x00": true, "\x03\x00": true,
},
},
// Checks that special ints are not used.
{
"Special ints test",
"\xcd\xab",
CompMap{0xabcd: uint64Set{0x1: true, 0x2: true}},
map[string]bool{
"\x02\x00": true,
},
},
// Checks that ints of various sizes are extracted.
{
"Different sizes test",
"\xef\xcd\xab\x90\x78\x56\x34\x12",
CompMap{
0xef: uint64Set{0x11: true},
0xcdef: uint64Set{0x2222: true},
0x90abcdef: uint64Set{0x33333333: true},
0x1234567890abcdef: uint64Set{0x4444444444444444: true},
},
map[string]bool{
"\x11\xcd\xab\x90\x78\x56\x34\x12": true,
"\x22\x22\xab\x90\x78\x56\x34\x12": true,
"\x33\x33\x33\x33\x78\x56\x34\x12": true,
"\x44\x44\x44\x44\x44\x44\x44\x44": true,
},
},
// Checks that values with different offsets are extracted.
{
"Different offsets test",
"\xab\xab\xab\xab\xab\xab\xab\xab\xab",
CompMap{
0xab: uint64Set{0x11: true},
0xabab: uint64Set{0x2222: true},
0xabababab: uint64Set{0x33333333: true},
0xabababababababab: uint64Set{0x4444444444444444: true},
},
map[string]bool{
"\x11\xab\xab\xab\xab\xab\xab\xab\xab": true,
"\xab\x11\xab\xab\xab\xab\xab\xab\xab": true,
"\xab\xab\x11\xab\xab\xab\xab\xab\xab": true,
"\xab\xab\xab\x11\xab\xab\xab\xab\xab": true,
"\xab\xab\xab\xab\x11\xab\xab\xab\xab": true,
"\xab\xab\xab\xab\xab\x11\xab\xab\xab": true,
"\xab\xab\xab\xab\xab\xab\x11\xab\xab": true,
"\xab\xab\xab\xab\xab\xab\xab\x11\xab": true,
"\xab\xab\xab\xab\xab\xab\xab\xab\x11": true,
"\x22\x22\xab\xab\xab\xab\xab\xab\xab": true,
"\xab\x22\x22\xab\xab\xab\xab\xab\xab": true,
"\xab\xab\x22\x22\xab\xab\xab\xab\xab": true,
"\xab\xab\xab\x22\x22\xab\xab\xab\xab": true,
"\xab\xab\xab\xab\x22\x22\xab\xab\xab": true,
"\xab\xab\xab\xab\xab\x22\x22\xab\xab": true,
"\xab\xab\xab\xab\xab\xab\x22\x22\xab": true,
"\xab\xab\xab\xab\xab\xab\xab\x22\x22": true,
"\x33\x33\x33\x33\xab\xab\xab\xab\xab": true,
"\xab\x33\x33\x33\x33\xab\xab\xab\xab": true,
"\xab\xab\x33\x33\x33\x33\xab\xab\xab": true,
"\xab\xab\xab\x33\x33\x33\x33\xab\xab": true,
"\xab\xab\xab\xab\x33\x33\x33\x33\xab": true,
"\xab\xab\xab\xab\xab\x33\x33\x33\x33": true,
"\x44\x44\x44\x44\x44\x44\x44\x44\xab": true,
"\xab\x44\x44\x44\x44\x44\x44\x44\x44": true,
},
},
{
"Replace in the middle of a larger blob",
"\xef\xcd\xab\x90\x78\x56\x34\x12",
CompMap{0xffffffffffff90ab: uint64Set{0xffffffffffffaabb: true}},
map[string]bool{
"\xef\xcd\xbb\xaa\x78\x56\x34\x12": true,
},
},
{
"Big-endian replace",
"\xef\xcd\xab\x90\x78\x56\x34\x12",
CompMap{
// 0xff07 is reversed special int.
0xefcd: uint64Set{0xaabb: true, 0xff07: true},
0x3412: uint64Set{0xaabb: true, 0xff07: true},
0x9078: uint64Set{0xaabb: true, 0x11223344: true, 0xff07: true},
0x90785634: uint64Set{0xaabbccdd: true, 0x11223344: true},
0xefcdab9078563412: uint64Set{0x1122334455667788: true},
},
map[string]bool{
"\xaa\xbb\xab\x90\x78\x56\x34\x12": true,
"\xef\xcd\xab\x90\x78\x56\xaa\xbb": true,
"\xef\xcd\xab\xaa\xbb\x56\x34\x12": true,
"\xef\xcd\xab\xaa\xbb\xcc\xdd\x12": true,
"\xef\xcd\xab\x11\x22\x33\x44\x12": true,
"\x11\x22\x33\x44\x55\x66\x77\x88": true,
},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
t.Parallel()
res := make(map[string]bool)
// Whatever type here. It's just needed to pass the
// dataArg.Type().Dir() == DirIn check.
typ := &ArrayType{TypeCommon{"", "", 0, DirIn, false, true}, nil, 0, 0, 0}
dataArg := MakeDataArg(typ, []byte(test.in))
checkDataArg(dataArg, test.comps, func() {
res[string(dataArg.Data())] = true
})
if !reflect.DeepEqual(res, test.res) {
s := "\ngot: ["
for x := range res {
s += fmt.Sprintf("0x%x, ", x)
}
s += "]\nwant: ["
for x := range test.res {
s += fmt.Sprintf("0x%x, ", x)
}
s += "]\n"
t.Fatalf(s)
}
})
}
}
func TestHintsShrinkExpand(t *testing.T) {
t.Parallel()
// Naming conventions:
// b - byte variable (i8 or u8)
// w - word variable (i16 or u16)
// dw - dword variable (i32 or u32)
// qw - qword variable (i64 or u64)
// -----------------------------------------------------------------
// Shrink tests:
var tests = []ConstArgTest{
{
// Models the following code:
// void f(u16 w) {
// u8 b = (u8) w;
// if (b == 0xab) {...}
// if (w == 0xcdcd) {...}
// }; f(0x1234);
"Shrink 16 test",
0x1234,
CompMap{
0x34: uint64Set{0xab: true},
0x1234: uint64Set{0xcdcd: true},
},
uint64Set{0x12ab: true, 0xcdcd: true},
},
{
// Models the following code:
// void f(u32 dw) {
// u8 b = (u8) dw
// i16 w = (i16) dw
// if (a == 0xab) {...}
// if (b == 0xcdcd) {...}
// if (dw == 0xefefefef) {...}
// }; f(0x12345678);
"Shrink 32 test",
0x12345678,
CompMap{
0x78: uint64Set{0xab: true},
0x5678: uint64Set{0xcdcd: true},
0x12345678: uint64Set{0xefefefef: true},
},
uint64Set{0x123456ab: true, 0x1234cdcd: true, 0xefefefef: true},
},
{
// Models the following code:
// void f(u64 qw) {
// u8 b = (u8) qw
// u16 w = (u16) qw
// u32 dw = (u32) qw
// if (a == 0xab) {...}
// if (b == 0xcdcd) {...}
// if (dw == 0xefefefef) {...}
// if (qw == 0x0101010101010101) {...}
// }; f(0x1234567890abcdef);
"Shrink 64 test",
0x1234567890abcdef,
CompMap{
0xef: uint64Set{0xab: true},
0xcdef: uint64Set{0xcdcd: true},
0x90abcdef: uint64Set{0xefefefef: true},
0x1234567890abcdef: uint64Set{0x0101010101010101: true},
},
uint64Set{
0x1234567890abcdab: true,
0x1234567890abcdcd: true,
0x12345678efefefef: true,
0x0101010101010101: true,
},
},
{
// Models the following code:
// void f(i16 w) {
// i8 b = (i8) w;
// i16 other = 0xabab;
// if (b == other) {...}
// }; f(0x1234);
// In such code the comparison will never be true, so we don't
// generate a hint for it.
"Shrink with a wider replacer test1",
0x1234,
CompMap{0x34: uint64Set{0x1bab: true}},
nil,
},
{
// Models the following code:
// void f(i16 w) {
// i8 b = (i8) w;
// i16 other = 0xfffd;
// if (b == other) {...}
// }; f(0x1234);
// In such code b will be sign extended to 0xff34 and, if we replace
// the lower byte, then the if statement will be true.
// Note that executor sign extends all the comparison operands to
// int64, so we model this accordingly.
"Shrink with a wider replacer test2",
0x1234,
CompMap{0x34: uint64Set{0xfffffffffffffffd: true}},
uint64Set{0x12fd: true},
},
// -----------------------------------------------------------------
// Extend tests:
// Note that executor sign extends all the comparison operands to int64,
// so we model this accordingly.
{
// Models the following code:
// void f(i8 b) {
// i64 qw = (i64) b;
// if (qw == -2) {...};
// }; f(-1);
"Extend 8 test",
0xff,
CompMap{0xffffffffffffffff: uint64Set{0xfffffffffffffffe: true}},
uint64Set{0xfe: true},
},
{
// Models the following code:
// void f(i16 w) {
// i64 qw = (i64) w;
// if (qw == -2) {...};
// }; f(-1);
"Extend 16 test",
0xffff,
CompMap{0xffffffffffffffff: uint64Set{0xfffffffffffffffe: true}},
uint64Set{0xfffe: true},
},
{
// Models the following code:
// void f(i32 dw) {
// i64 qw = (i32) dw;
// if (qw == -2) {...};
// }; f(-1);
"Extend 32 test",
0xffffffff,
CompMap{0xffffffffffffffff: uint64Set{0xfffffffffffffffe: true}},
uint64Set{0xfffffffe: true},
},
{
// Models the following code:
// void f(i8 b) {
// i16 w = (i16) b;
// if (w == (i16) 0xfeff) {...};
// }; f(-1);
// There's no value for b that will make the comparison true,
// so we don't generate hints.
"Extend with a wider replacer test",
0xff,
CompMap{0xffffffffffffffff: uint64Set{0xfffffffffffffeff: true}},
nil,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
t.Parallel()
res := shrinkExpand(test.in, test.comps)
if !reflect.DeepEqual(res, test.res) {
t.Fatalf("\ngot : %v\nwant: %v", res, test.res)
}
})
}
}
func TestHintsRandom(t *testing.T) {
target, rs, iters := initTest(t)
iters /= 10 // the test takes long
r := newRand(target, rs)
for i := 0; i < iters; i++ {
p := target.Generate(rs, 5, nil)
for i, c := range p.Calls {
vals := extractValues(c)
for j := 0; j < 5; j++ {
vals[r.randInt()] = true
}
comps := make(CompMap)
for v := range vals {
comps.AddComp(v, r.randInt())
}
p.MutateWithHints(i, comps, func(p1 *Prog) {})
}
}
}
func extractValues(c *Call) map[uint64]bool {
vals := make(map[uint64]bool)
ForeachArg(c, func(arg Arg, _ *ArgCtx) {
if typ := arg.Type(); typ == nil || typ.Dir() == DirOut {
return
}
switch a := arg.(type) {
case *ConstArg:
vals[a.Val] = true
case *DataArg:
data := a.Data()
for i := range data {
vals[uint64(data[i])] = true
if i < len(data)-1 {
v := uint64(data[i]) | uint64(data[i+1])<<8
vals[v] = true
}
if i < len(data)-3 {
v := uint64(data[i]) | uint64(data[i+1])<<8 |
uint64(data[i+2])<<16 | uint64(data[i+3])<<24
vals[v] = true
}
if i < len(data)-7 {
v := uint64(data[i]) | uint64(data[i+1])<<8 |
uint64(data[i+2])<<16 | uint64(data[i+3])<<24 |
uint64(data[i+4])<<32 | uint64(data[i+5])<<40 |
uint64(data[i+6])<<48 | uint64(data[i+7])<<56
vals[v] = true
}
}
}
})
return vals
}
func TestHintsData(t *testing.T) {
target := initTargetTest(t, "test", "64")
type Test struct {
in string
comps CompMap
out []string
}
tests := []Test{
{
in: "0809101112131415",
comps: CompMap{0x12111009: uint64Set{0x10: true}},
out: []string{"0810000000131415"},
},
}
call := target.SyscallMap["test$hint_data"]
for _, test := range tests {
input, err := hex.DecodeString(test.in)
if err != nil {
t.Fatal(err)
}
p := &Prog{
Target: target,
Calls: []*Call{{
Meta: call,
Args: []Arg{MakePointerArg(call.Args[0], 0,
MakeDataArg(call.Args[0].(*PtrType).Type, input))},
Ret: MakeReturnArg(call.Ret),
}},
}
if err := p.validate(); err != nil {
t.Fatal(err)
}
var got []string
p.MutateWithHints(0, test.comps, func(newP *Prog) {
got = append(got, hex.EncodeToString(
newP.Calls[0].Args[0].(*PointerArg).Res.(*DataArg).Data()))
})
sort.Strings(test.out)
sort.Strings(got)
if !reflect.DeepEqual(got, test.out) {
t.Fatalf("comps: %v\ninput: %v\ngot : %+v\nwant: %+v",
test.comps, test.in, got, test.out)
}
}
}
func BenchmarkHints(b *testing.B) {
olddebug := debug
debug = false
defer func() { debug = olddebug }()
target, err := GetTarget("linux", "amd64")
if err != nil {
b.Fatal(err)
}
rs := rand.NewSource(0)
r := newRand(target, rs)
p := target.Generate(rs, 30, nil)
comps := make([]CompMap, len(p.Calls))
for i, c := range p.Calls {
vals := extractValues(c)
for j := 0; j < 5; j++ {
vals[r.randInt()] = true
}
comps[i] = make(CompMap)
for v := range vals {
comps[i].AddComp(v, r.randInt())
}
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := range p.Calls {
p.MutateWithHints(i, comps[i], func(p1 *Prog) {})
}
}
})
}