| // 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 |
| } |