blob: d0013b75d04af8faed5b7bf30f9a0700ce425c64 [file] [log] [blame] [edit]
// Copyright 2015/2016 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 (
"fmt"
"math/rand"
"sort"
)
// Calulation of call-to-call priorities.
// For a given pair of calls X and Y, the priority is our guess as to whether
// additional of call Y into a program containing call X is likely to give
// new coverage or not.
// The current algorithm has two components: static and dynamic.
// The static component is based on analysis of argument types. For example,
// if call X and call Y both accept fd[sock], then they are more likely to give
// new coverage together.
// The dynamic component is based on frequency of occurrence of a particular
// pair of syscalls in a single program in corpus. For example, if socket and
// connect frequently occur in programs together, we give higher priority to
// this pair of syscalls.
// Note: the current implementation is very basic, there is no theory behind any
// constants.
func (target *Target) CalculatePriorities(corpus []*Prog) [][]int32 {
static := target.calcStaticPriorities()
if len(corpus) != 0 {
dynamic := target.calcDynamicPrio(corpus)
for i, prios := range dynamic {
dst := static[i]
for j, p := range prios {
dst[j] = dst[j] * p / prioHigh
}
}
}
return static
}
func (target *Target) calcStaticPriorities() [][]int32 {
uses := target.calcResourceUsage()
prios := make([][]int32, len(target.Syscalls))
for i := range prios {
prios[i] = make([]int32, len(target.Syscalls))
}
for _, weights := range uses {
for _, w0 := range weights {
for _, w1 := range weights {
if w0.call == w1.call {
// Self-priority is assigned below.
continue
}
// The static priority is assigned based on the direction of arguments. A higher priority will be
// assigned when c0 is a call that produces a resource and c1 a call that uses that resource.
prios[w0.call][w1.call] += w0.inout*w1.in*3/2 + w0.inout*w1.inout
}
}
}
normalizePrio(prios)
// The value assigned for self-priority (call wrt itself) have to be high, but not too high.
for c0, pp := range prios {
pp[c0] = prioHigh * 9 / 10
}
return prios
}
func (target *Target) calcResourceUsage() map[string]map[int]weights {
uses := make(map[string]map[int]weights)
ForeachType(target.Syscalls, func(t Type, ctx *TypeCtx) {
c := ctx.Meta
switch a := t.(type) {
case *ResourceType:
if target.AuxResources[a.Desc.Name] {
noteUsage(uses, c, 1, ctx.Dir, "res%v", a.Desc.Name)
} else {
str := "res"
for i, k := range a.Desc.Kind {
str += "-" + k
w := int32(10)
if i < len(a.Desc.Kind)-1 {
w = 2
}
noteUsage(uses, c, w, ctx.Dir, str)
}
}
case *PtrType:
if _, ok := a.Elem.(*StructType); ok {
noteUsage(uses, c, 10, ctx.Dir, "ptrto-%v", a.Elem.Name())
}
if _, ok := a.Elem.(*UnionType); ok {
noteUsage(uses, c, 10, ctx.Dir, "ptrto-%v", a.Elem.Name())
}
if arr, ok := a.Elem.(*ArrayType); ok {
noteUsage(uses, c, 10, ctx.Dir, "ptrto-%v", arr.Elem.Name())
}
case *BufferType:
switch a.Kind {
case BufferBlobRand, BufferBlobRange, BufferText:
case BufferString, BufferGlob:
if a.SubKind != "" {
noteUsage(uses, c, 2, ctx.Dir, fmt.Sprintf("str-%v", a.SubKind))
}
case BufferFilename:
noteUsage(uses, c, 10, DirIn, "filename")
default:
panic("unknown buffer kind")
}
case *VmaType:
noteUsage(uses, c, 5, ctx.Dir, "vma")
case *IntType:
switch a.Kind {
case IntPlain, IntRange:
default:
panic("unknown int kind")
}
}
})
return uses
}
type weights struct {
call int
in int32
inout int32
}
func noteUsage(uses map[string]map[int]weights, c *Syscall, weight int32, dir Dir, str string, args ...interface{}) {
id := fmt.Sprintf(str, args...)
if uses[id] == nil {
uses[id] = make(map[int]weights)
}
callWeight := uses[id][c.ID]
callWeight.call = c.ID
if dir != DirOut {
if weight > uses[id][c.ID].in {
callWeight.in = weight
}
}
if weight > uses[id][c.ID].inout {
callWeight.inout = weight
}
uses[id][c.ID] = callWeight
}
func (target *Target) calcDynamicPrio(corpus []*Prog) [][]int32 {
prios := make([][]int32, len(target.Syscalls))
for i := range prios {
prios[i] = make([]int32, len(target.Syscalls))
}
for _, p := range corpus {
for idx0, c0 := range p.Calls {
for _, c1 := range p.Calls[idx0+1:] {
prios[c0.Meta.ID][c1.Meta.ID]++
}
}
}
normalizePrio(prios)
return prios
}
const (
prioLow = 10
prioHigh = 1000
)
// normalizePrio normalizes priorities to [prioLow..prioHigh] range.
func normalizePrio(prios [][]int32) {
for _, prio := range prios {
max := int32(1)
for _, p := range prio {
if max < p {
max = p
}
}
for i, p := range prio {
prio[i] = prioLow + p*(prioHigh-prioLow)/max
}
}
}
// ChooseTable allows to do a weighted choice of a syscall for a given syscall
// based on call-to-call priorities and a set of enabled and generatable syscalls.
type ChoiceTable struct {
target *Target
runs [][]int32
calls []*Syscall
noGenerateCalls map[int]bool
}
func (target *Target) BuildChoiceTable(corpus []*Prog, enabled map[*Syscall]bool) *ChoiceTable {
if enabled == nil {
enabled = make(map[*Syscall]bool)
for _, c := range target.Syscalls {
enabled[c] = true
}
}
noGenerateCalls := make(map[int]bool)
for call := range enabled {
if call.Attrs.Disabled {
delete(enabled, call)
} else if call.Attrs.NoGenerate {
noGenerateCalls[call.ID] = true
delete(enabled, call)
}
}
var generatableCalls []*Syscall
for c := range enabled {
generatableCalls = append(generatableCalls, c)
}
if len(generatableCalls) == 0 {
panic("no syscalls enabled and generatable")
}
sort.Slice(generatableCalls, func(i, j int) bool {
return generatableCalls[i].ID < generatableCalls[j].ID
})
for _, p := range corpus {
for _, call := range p.Calls {
if !enabled[call.Meta] && !noGenerateCalls[call.Meta.ID] {
fmt.Printf("corpus contains disabled syscall %v\n", call.Meta.Name)
panic("disabled syscall")
}
}
}
prios := target.CalculatePriorities(corpus)
run := make([][]int32, len(target.Syscalls))
for i := range run {
if !enabled[target.Syscalls[i]] {
continue
}
run[i] = make([]int32, len(target.Syscalls))
var sum int32
for j := range run[i] {
if enabled[target.Syscalls[j]] {
sum += prios[i][j]
}
run[i][j] = sum
}
}
return &ChoiceTable{target, run, generatableCalls, noGenerateCalls}
}
func (ct *ChoiceTable) Enabled(call int) bool {
return ct.Generatable(call) || ct.noGenerateCalls[call]
}
func (ct *ChoiceTable) Generatable(call int) bool {
return ct.runs[call] != nil
}
func (ct *ChoiceTable) choose(r *rand.Rand, bias int) int {
if bias < 0 {
bias = ct.calls[r.Intn(len(ct.calls))].ID
}
if !ct.Generatable(bias) {
fmt.Printf("bias to disabled or non-generatable syscall %v\n", ct.target.Syscalls[bias].Name)
panic("disabled or non-generatable syscall")
}
run := ct.runs[bias]
x := int32(r.Intn(int(run[len(run)-1])) + 1)
res := sort.Search(len(run), func(i int) bool {
return run[i] >= x
})
if !ct.Generatable(res) {
panic("selected disabled or non-generatable syscall")
}
return res
}