• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 syzkaller project authors. All rights reserved.
2// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3
4package prog
5
6// A hint is basically a tuple consisting of a pointer to an argument
7// in one of the syscalls of a program and a value, which should be
8// assigned to that argument (we call it a replacer).
9
10// A simplified version of hints workflow looks like this:
11//		1. Fuzzer launches a program (we call it a hint seed) and collects all
12// the comparisons' data for every syscall in the program.
13//		2. Next it tries to match the obtained comparison operands' values
14// vs. the input arguments' values.
15//		3. For every such match the fuzzer mutates the program by
16// replacing the pointed argument with the saved value.
17//		4. If a valid program is obtained, then fuzzer launches it and
18// checks if new coverage is obtained.
19// For more insights on particular mutations please see prog/hints_test.go.
20
21import (
22	"bytes"
23	"encoding/binary"
24	"fmt"
25)
26
27type uint64Set map[uint64]bool
28
29// Example: for comparisons {(op1, op2), (op1, op3), (op1, op4), (op2, op1)}
30// this map will store the following:
31// m = {
32//		op1: {map[op2]: true, map[op3]: true, map[op4]: true},
33//		op2: {map[op1]: true}
34// }.
35type CompMap map[uint64]uint64Set
36
37const (
38	maxDataLength = 100
39)
40
41var specialIntsSet uint64Set
42
43func (m CompMap) AddComp(arg1, arg2 uint64) {
44	if _, ok := m[arg1]; !ok {
45		m[arg1] = make(uint64Set)
46	}
47	m[arg1][arg2] = true
48}
49
50func (m CompMap) String() string {
51	buf := new(bytes.Buffer)
52	for v, comps := range m {
53		if len(buf.Bytes()) != 0 {
54			fmt.Fprintf(buf, ", ")
55		}
56		fmt.Fprintf(buf, "0x%x:", v)
57		for c := range comps {
58			fmt.Fprintf(buf, " 0x%x", c)
59		}
60	}
61	return buf.String()
62}
63
64// Mutates the program using the comparison operands stored in compMaps.
65// For each of the mutants executes the exec callback.
66func (p *Prog) MutateWithHints(callIndex int, comps CompMap, exec func(p *Prog)) {
67	p = p.Clone()
68	c := p.Calls[callIndex]
69	execValidate := func() {
70		p.Target.SanitizeCall(c)
71		p.debugValidate()
72		exec(p)
73	}
74	ForeachArg(c, func(arg Arg, _ *ArgCtx) {
75		generateHints(comps, arg, execValidate)
76	})
77}
78
79func generateHints(compMap CompMap, arg Arg, exec func()) {
80	typ := arg.Type()
81	if typ == nil || typ.Dir() == DirOut {
82		return
83	}
84	switch t := typ.(type) {
85	case *ProcType:
86		// Random proc will not pass validation.
87		// We can mutate it, but only if the resulting value is within the legal range.
88		return
89	case *CsumType:
90		// Csum will not pass validation and is always computed.
91		return
92	case *BufferType:
93		if t.Kind == BufferFilename {
94			// This can generate escaping paths and is probably not too useful anyway.
95			return
96		}
97	}
98
99	switch a := arg.(type) {
100	case *ConstArg:
101		checkConstArg(a, compMap, exec)
102	case *DataArg:
103		checkDataArg(a, compMap, exec)
104	}
105}
106
107func checkConstArg(arg *ConstArg, compMap CompMap, exec func()) {
108	original := arg.Val
109	for replacer := range shrinkExpand(original, compMap) {
110		arg.Val = replacer
111		exec()
112	}
113	arg.Val = original
114}
115
116func checkDataArg(arg *DataArg, compMap CompMap, exec func()) {
117	bytes := make([]byte, 8)
118	data := arg.Data()
119	size := len(data)
120	if size > maxDataLength {
121		size = maxDataLength
122	}
123	for i := 0; i < size; i++ {
124		original := make([]byte, 8)
125		copy(original, data[i:])
126		val := binary.LittleEndian.Uint64(original)
127		for replacer := range shrinkExpand(val, compMap) {
128			binary.LittleEndian.PutUint64(bytes, replacer)
129			copy(data[i:], bytes)
130			exec()
131		}
132		copy(data[i:], original)
133	}
134}
135
136// Shrink and expand mutations model the cases when the syscall arguments
137// are casted to narrower (and wider) integer types.
138// ======================================================================
139// Motivation for shrink:
140// void f(u16 x) {
141//		u8 y = (u8)x;
142//		if (y == 0xab) {...}
143// }
144// If we call f(0x1234), then we'll see a comparison 0x34 vs 0xab and we'll
145// be unable to match the argument 0x1234 with any of the comparison operands.
146// Thus we shrink 0x1234 to 0x34 and try to match 0x34.
147// If there's a match for the shrank value, then we replace the corresponding
148// bytes of the input (in the given example we'll get 0x12ab).
149// Sometimes the other comparison operand will be wider than the shrank value
150// (in the example above consider comparison if (y == 0xdeadbeef) {...}).
151// In this case we ignore such comparison because we couldn't come up with
152// any valid code example that does similar things. To avoid such comparisons
153// we check the sizes with leastSize().
154// ======================================================================
155// Motivation for expand:
156// void f(i8 x) {
157//		i16 y = (i16)x;
158//		if (y == -2) {...}
159// }
160// Suppose we call f(-1), then we'll see a comparison 0xffff vs 0xfffe and be
161// unable to match input vs any operands. Thus we sign extend the input and
162// check the extension.
163// As with shrink we ignore cases when the other operand is wider.
164// Note that executor sign extends all the comparison operands to int64.
165// ======================================================================
166func shrinkExpand(v uint64, compMap CompMap) (replacers uint64Set) {
167	for _, iwidth := range []int{8, 4, 2, 1, -4, -2, -1} {
168		var width int
169		var size, mutant uint64
170		if iwidth > 0 {
171			width = iwidth
172			size = uint64(width) * 8
173			mutant = v & ((1 << size) - 1)
174		} else {
175			width = -iwidth
176			size = uint64(width) * 8
177			mutant = v | ^((1 << size) - 1)
178		}
179		// Use big-endian match/replace for both blobs and ints.
180		// Sometimes we have unmarked blobs (no little/big-endian info);
181		// for ANYBLOBs we intentionally lose all marking;
182		// but even for marked ints we may need this too.
183		// Consider that kernel code does not convert the data
184		// (i.e. not ntohs(pkt->proto) == ETH_P_BATMAN),
185		// but instead converts the constant (i.e. pkt->proto == htons(ETH_P_BATMAN)).
186		// In such case we will see dynamic operand that does not match what we have in the program.
187		for _, bigendian := range []bool{false, true} {
188			if bigendian {
189				if width == 1 {
190					continue
191				}
192				mutant = swapInt(mutant, width)
193			}
194			for newV := range compMap[mutant] {
195				mask := uint64(1<<size - 1)
196				newHi := newV & ^mask
197				newV = newV & mask
198				if newHi != 0 && newHi^^mask != 0 {
199					continue
200				}
201				if bigendian {
202					newV = swapInt(newV, width)
203				}
204				if specialIntsSet[newV] {
205					continue
206				}
207				// Replace size least significant bits of v with
208				// corresponding bits of newV. Leave the rest of v as it was.
209				replacer := (v &^ mask) | newV
210				// TODO(dvyukov): should we try replacing with arg+/-1?
211				// This could trigger some off-by-ones.
212				if replacers == nil {
213					replacers = make(uint64Set)
214				}
215				replacers[replacer] = true
216			}
217		}
218	}
219	return
220}
221
222func init() {
223	specialIntsSet = make(uint64Set)
224	for _, v := range specialInts {
225		specialIntsSet[v] = true
226	}
227}
228