syzkaller/prog/hints_test.go
Dmitry Vyukov 4016fc5ad7 prog: fix hints of data args
Hints for data args don't work.
We do all the work, but at the final stage we patch
arg in the _old_ program, not in the _new_ one.
So programs passed to the callback are all the same
and don't contain any mutations.
Tests did not catch this because they work right before that point
(don't test the actual interface function MutateWithHints).

Fix that and add a test that catches this.
2017-12-08 10:45:11 +01:00

453 lines
12 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"
"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) {
res := uint64Set{}
constArg := &ConstArg{ArgCommon{nil}, test.in}
checkConstArg(constArg, test.comps, func(arg Arg) {
res[arg.(*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,
},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
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}, nil, 0, 0, 0}
dataArg := &DataArg{ArgCommon{&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}},
uint64Set{},
},
{
// 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}},
uint64Set{},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%v", test.name), func(t *testing.T) {
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, _ *[]Arg) {
switch a := arg.(type) {
case *ConstArg:
vals[a.Val] = true
case *DataArg:
for i := range a.Data {
vals[uint64(a.Data[i])] = true
if i < len(a.Data)-1 {
v := uint64(a.Data[i]) | uint64(a.Data[i+1])<<8
vals[v] = true
}
if i < len(a.Data)-3 {
v := uint64(a.Data[i]) | uint64(a.Data[i+1])<<8 |
uint64(a.Data[i+2])<<16 | uint64(a.Data[i+3])<<24
vals[v] = true
}
if i < len(a.Data)-7 {
v := uint64(a.Data[i]) | uint64(a.Data[i+1])<<8 |
uint64(a.Data[i+2])<<16 | uint64(a.Data[i+3])<<24 |
uint64(a.Data[i+4])<<32 | uint64(a.Data[i+5])<<40 |
uint64(a.Data[i+6])<<48 | uint64(a.Data[i+7])<<56
vals[v] = true
}
}
}
})
return vals
}
func TestHintsData(t *testing.T) {
t.Parallel()
type Test struct {
in string
comps CompMap
out []string
}
tests := []Test{
{
in: "0809101112131415",
comps: CompMap{0x12111009: uint64Set{0x10: true}},
out: []string{"0810000000131415"},
},
}
target, err := GetTarget("linux", "amd64")
if err != nil {
t.Fatal(err)
}
var call *Syscall
for _, c := range target.Syscalls {
if c.Name == "syz_test$hint_data" {
call = c
break
}
}
if call == nil {
t.Fatalf("can't find syz_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, 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: %s\ninput: %v\ngot : %+v\nwant: %+v",
test.comps, test.in, got, test.out)
}
}
}