Home | History | Annotate | Download | only in prog
      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 
      4 package 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 
     21 import (
     22 	"bytes"
     23 	"encoding/binary"
     24 	"fmt"
     25 )
     26 
     27 type 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 // }.
     35 type CompMap map[uint64]uint64Set
     36 
     37 const (
     38 	maxDataLength = 100
     39 )
     40 
     41 var specialIntsSet uint64Set
     42 
     43 func (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 
     50 func (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.
     66 func (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 
     79 func 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 
    107 func 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 
    116 func 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 // ======================================================================
    166 func 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 
    222 func init() {
    223 	specialIntsSet = make(uint64Set)
    224 	for _, v := range specialInts {
    225 		specialIntsSet[v] = true
    226 	}
    227 }
    228