// 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 syscalls. type ChoiceTable struct { target *Target runs [][]int32 calls []*Syscall } 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 } } for call := range enabled { if call.Attrs.Disabled { delete(enabled, call) } } var enabledCalls []*Syscall for c := range enabled { enabledCalls = append(enabledCalls, c) } if len(enabledCalls) == 0 { panic("no syscalls enabled") } sort.Slice(enabledCalls, func(i, j int) bool { return enabledCalls[i].ID < enabledCalls[j].ID }) for _, p := range corpus { for _, call := range p.Calls { if !enabled[call.Meta] { 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, enabledCalls} } func (ct *ChoiceTable) Enabled(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.Enabled(bias) { fmt.Printf("bias to disabled syscall %v\n", ct.target.Syscalls[bias].Name) panic("disabled 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.Enabled(res) { panic("selected disabled syscall") } return res }