syzkaller/prog/prio.go

261 lines
6.6 KiB
Go
Raw Normal View History

// Copyright 2015/2016 syzkaller project authors. All rights reserved.
2015-10-14 14:55:09 +00:00
// 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) [][]float32 {
static := target.calcStaticPriorities()
dynamic := target.calcDynamicPrio(corpus)
2015-10-14 14:55:09 +00:00
for i, prios := range static {
for j, p := range prios {
dynamic[i][j] *= p
}
}
return dynamic
}
func (target *Target) calcStaticPriorities() [][]float32 {
uses := target.calcResourceUsage()
prios := make([][]float32, len(target.Syscalls))
for i := range prios {
prios[i] = make([]float32, len(target.Syscalls))
}
for _, calls := range uses {
for c0, w0 := range calls {
for c1, w1 := range calls {
if c0 == c1 {
// 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[c0][c1] += w0.inout*w1.in + 0.7*w0.inout*w1.inout
2015-10-14 14:55:09 +00:00
}
}
}
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] = 0.9
}
return prios
}
func (target *Target) calcResourceUsage() map[string]map[int]weights {
uses := make(map[string]map[int]weights)
for _, c := range target.Syscalls {
ForeachType(c, func(t Type) {
2015-10-14 14:55:09 +00:00
switch a := t.(type) {
case *ResourceType:
if target.AuxResources[a.Desc.Name] {
noteUsage(uses, c, 0.1, a.Dir(), "res%v", a.Desc.Name)
2015-10-14 14:55:09 +00:00
} else {
str := "res"
for i, k := range a.Desc.Kind {
str += "-" + k
w := 1.0
if i < len(a.Desc.Kind)-1 {
w = 0.2
}
noteUsage(uses, c, float32(w), a.Dir(), str)
}
2015-10-14 14:55:09 +00:00
}
case *PtrType:
if _, ok := a.Type.(*StructType); ok {
noteUsage(uses, c, 1.0, a.Dir(), "ptrto-%v", a.Type.Name())
2015-10-14 14:55:09 +00:00
}
if _, ok := a.Type.(*UnionType); ok {
noteUsage(uses, c, 1.0, a.Dir(), "ptrto-%v", a.Type.Name())
2015-12-29 14:00:57 +00:00
}
if arr, ok := a.Type.(*ArrayType); ok {
noteUsage(uses, c, 1.0, a.Dir(), "ptrto-%v", arr.Type.Name())
2015-12-29 14:00:57 +00:00
}
case *BufferType:
2015-10-14 14:55:09 +00:00
switch a.Kind {
case BufferBlobRand, BufferBlobRange, BufferText:
case BufferString:
if a.SubKind != "" {
noteUsage(uses, c, 0.2, a.Dir(), fmt.Sprintf("str-%v", a.SubKind))
}
case BufferFilename:
noteUsage(uses, c, 1.0, DirIn, "filename")
2015-10-14 14:55:09 +00:00
default:
panic("unknown buffer kind")
}
case *VmaType:
noteUsage(uses, c, 0.5, a.Dir(), "vma")
case *IntType:
2015-10-14 14:55:09 +00:00
switch a.Kind {
case IntPlain, IntRange:
2015-10-14 14:55:09 +00:00
default:
panic("unknown int kind")
}
}
})
}
return uses
}
type weights struct {
in float32
inout float32
}
func noteUsage(uses map[string]map[int]weights, c *Syscall, weight float32, 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]
if dir != DirOut {
if weight > uses[id][c.ID].in {
callWeight.in = weight
}
}
if weight > uses[id][c.ID].inout {
callWeight.inout = weight
2015-10-14 14:55:09 +00:00
}
uses[id][c.ID] = callWeight
2015-10-14 14:55:09 +00:00
}
func (target *Target) calcDynamicPrio(corpus []*Prog) [][]float32 {
prios := make([][]float32, len(target.Syscalls))
2015-10-14 14:55:09 +00:00
for i := range prios {
prios[i] = make([]float32, len(target.Syscalls))
2015-10-14 14:55:09 +00:00
}
for _, p := range corpus {
for idx0, c0 := range p.Calls {
for _, c1 := range p.Calls[idx0+1:] {
prog: fix dynamic prio calculation Dynamic prio is meant to prioritize calls that are already used together in existing programs. The calculation used call index in the program instead of call ID, which does not make any sense and is a plain bug. It prioritized calls starting from 'a' (as syscalls are sorted). Use call ID for dynamic prio calculation. Static prios for add_key: 1.0000 keyctl$search 1.0000 request_key 1.0000 add_key 0.5411 keyctl$assume_authority 0.5411 keyctl$setperm 0.5411 keyctl$set_timeout 0.5411 keyctl$unlink 0.5411 keyctl$revoke 0.5411 keyctl$reject 0.5411 keyctl$read 0.5411 keyctl$negate 0.5411 keyctl$link 0.5411 keyctl$join 0.5411 keyctl$invalidate 0.5411 keyctl$instantiate_iov 0.5411 keyctl$instantiate 0.5411 keyctl$get_security 0.5411 keyctl$get_persistent 0.5411 keyctl$update Dynamic prios before fix: 0.1000 accept 0.1000 accept$alg 0.1000 accept$ax25 0.1000 accept$inet 0.1000 accept$inet6 0.1000 accept$inet_sctp 0.1000 accept$ipx 0.1000 accept$netrom 0.1000 accept$nfc_llcp 0.1000 accept$unix 0.1000 accept4 0.1000 accept4$ax25 0.1000 accept4$inet 0.1000 accept4$inet6 0.1000 accept4$inet_sctp 0.1000 accept4$ipx 0.1000 accept4$unix 0.1000 acct Dynamic prios after fix: 0.2465 request_key 0.1142 keyctl$search 0.1000 add_key 0.1000 perf_event_open 0.0766 keyctl$invalidate 0.0717 keyctl$setperm 0.0717 keyctl$unlink 0.0717 keyctl$instantiate_iov 0.0681 keyctl$read 0.0649 keyctl$update 0.0649 keyctl$chown 0.0645 keyctl$link 0.0645 keyctl$get_security 0.0631 keyctl$revoke 0.0622 keyctl$clear 0.0622 keyctl$reject 0.0618 keyctl$set_timeout 0.0618 keyctl$negate 0.0613 keyctl$instantiate Fixes #164
2017-05-02 10:28:48 +00:00
id0 := c0.Meta.ID
id1 := c1.Meta.ID
prios[id0][id1] += 1.0
2015-10-14 14:55:09 +00:00
}
}
}
normalizePrio(prios)
return prios
}
// normalizePrio assigns some minimal priorities to calls with zero priority,
// and then normalizes priorities to 0.1..1 range.
func normalizePrio(prios [][]float32) {
for _, prio := range prios {
max := float32(0)
min := float32(1e10)
nzero := 0
for _, p := range prio {
if max < p {
max = p
}
if p != 0 && min > p {
min = p
}
if p == 0 {
nzero++
}
}
if nzero != 0 {
min /= 2 * float32(nzero)
}
if min == max {
max = 0
}
2015-10-14 14:55:09 +00:00
for i, p := range prio {
if max == 0 {
prio[i] = 1
continue
}
if p == 0 {
p = min
}
p = (p-min)/(max-min)*0.9 + 0.1
if p > 1 {
p = 1
}
prio[i] = p
}
}
}
// 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
2015-10-15 15:58:37 +00:00
run [][]int
enabledCalls []*Syscall
enabled map[*Syscall]bool
2015-10-14 14:55:09 +00:00
}
func (target *Target) BuildChoiceTable(prios [][]float32, enabled map[*Syscall]bool) *ChoiceTable {
if enabled == nil {
enabled = make(map[*Syscall]bool)
for _, c := range target.Syscalls {
enabled[c] = true
}
}
var enabledCalls []*Syscall
for c := range enabled {
enabledCalls = append(enabledCalls, c)
2015-10-14 14:55:09 +00:00
}
prog: detect invalid target.Syscalls in BuildChoiceTable Without this check programs may end up panicing in places far away from the real cause. E.g. worker# ./syz-fuzzer -executor=./syz-executor -name=vm-0 -arch=amd64 -manager=10.128.0.101:21386 -sandbox=setuid -procs=2 -v=0 -cover=true -debug=false -test=false 2004/02/03 12:11:11 fuzzer started 2004/02/03 12:11:11 dialing manager at 10.128.0.101:21386 2004/02/03 12:11:12 syscalls: 1 2004/02/03 12:11:12 code coverage: enabled 2004/02/03 12:11:12 comparison tracing: support is not implemented in syzkaller 2004/02/03 12:11:12 setuid sandbox: support is not implemented in syzkaller 2004/02/03 12:11:12 namespace sandbox: support is not implemented in syzkaller 2004/02/03 12:11:12 Android sandbox: support is not implemented in syzkaller 2004/02/03 12:11:12 fault injection: support is not implemented in syzkaller 2004/02/03 12:11:12 leak checking: support is not implemented in syzkaller 2004/02/03 12:11:12 net packet injection: enabled 2004/02/03 12:11:12 net device setup: support is not implemented in syzkaller panic: invalid argument to Intn goroutine 27 [running]: math/rand.(*Rand).Intn(0xc000dff530, 0x0, 0x40) /usr/local/go/src/math/rand/rand.go:169 +0x9c github.com/google/syzkaller/prog.(*ChoiceTable).Choose(0xc000d92ec0, 0xc000dff530, 0xffffffffffffffff, 0xc000dff650) /syzkaller/gopath/src/github.com/google/syzkaller/prog/prio.go:241 +0x1a0 github.com/google/syzkaller/prog.(*randGen).generateCall(0xc000e145a0, 0xc000c2a200, 0xc000ce7f80, 0x2348f1940, 0xc000ce3440, 0xc000e6ee01) /syzkaller/gopath/src/github.com/google/syzkaller/prog/rand.go:451 +0x69 github.com/google/syzkaller/prog.(*Target).Generate(0xc00007f1e0, 0x8f8680, 0xc000ce3440, 0x1e, 0xc000d92ec0, 0x0) /syzkaller/gopath/src/github.com/google/syzkaller/prog/generation.go:19 +0x2b2 main.(*Proc).loop(0xc000d92f40) /syzkaller/gopath/src/github.com/google/syzkaller/syz-fuzzer/proc.go:93 +0x2a1 created by main.main /syzkaller/gopath/src/github.com/google/syzkaller/syz-fuzzer/fuzzer.go:236 +0xfe2
2018-12-11 10:14:21 +00:00
if len(enabledCalls) == 0 {
panic(fmt.Sprintf("empty enabledCalls, len(target.Syscalls)=%v", len(target.Syscalls)))
}
run := make([][]int, len(target.Syscalls))
2015-10-14 14:55:09 +00:00
for i := range run {
if !enabled[target.Syscalls[i]] {
2015-10-14 14:55:09 +00:00
continue
}
run[i] = make([]int, len(target.Syscalls))
2015-10-14 14:55:09 +00:00
sum := 0
for j := range run[i] {
if enabled[target.Syscalls[j]] {
w := 1
if prios != nil {
w = int(prios[i][j] * 1000)
}
sum += w
2015-10-14 14:55:09 +00:00
}
run[i][j] = sum
}
}
return &ChoiceTable{target, run, enabledCalls, enabled}
2015-10-14 14:55:09 +00:00
}
func (ct *ChoiceTable) Choose(r *rand.Rand, call int) int {
2015-10-15 15:58:37 +00:00
if call < 0 {
return ct.enabledCalls[r.Intn(len(ct.enabledCalls))].ID
}
2015-10-14 14:55:09 +00:00
run := ct.run[call]
if run == nil {
2015-10-15 15:58:37 +00:00
return ct.enabledCalls[r.Intn(len(ct.enabledCalls))].ID
2015-10-14 14:55:09 +00:00
}
for {
x := r.Intn(run[len(run)-1]) + 1
2015-10-14 14:55:09 +00:00
i := sort.SearchInts(run, x)
if ct.enabled[ct.target.Syscalls[i]] {
return i
2015-10-14 14:55:09 +00:00
}
}
}