mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-27 05:10:43 +00:00
413b991c26
We are seeing some panics that say that some disabled syscalls somehow get into corpus. I don't see where/how this can happen. Add a check to syz-fuzzer to panic whenever we execute a program with disabled syscall. Hopefull the panic stack will shed some light. Also add a check in manager as the last defence line so that bad programs don't get into the corpus.
351 lines
10 KiB
Go
351 lines
10 KiB
Go
// Copyright 2017 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 main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"runtime/debug"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/google/syzkaller/pkg/cover"
|
|
"github.com/google/syzkaller/pkg/hash"
|
|
"github.com/google/syzkaller/pkg/ipc"
|
|
"github.com/google/syzkaller/pkg/log"
|
|
"github.com/google/syzkaller/pkg/rpctype"
|
|
"github.com/google/syzkaller/pkg/signal"
|
|
"github.com/google/syzkaller/prog"
|
|
)
|
|
|
|
// Proc represents a single fuzzing process (executor).
|
|
type Proc struct {
|
|
fuzzer *Fuzzer
|
|
pid int
|
|
env *ipc.Env
|
|
rnd *rand.Rand
|
|
execOpts *ipc.ExecOpts
|
|
execOptsCover *ipc.ExecOpts
|
|
execOptsComps *ipc.ExecOpts
|
|
execOptsNoCollide *ipc.ExecOpts
|
|
}
|
|
|
|
func newProc(fuzzer *Fuzzer, pid int) (*Proc, error) {
|
|
env, err := ipc.MakeEnv(fuzzer.config, pid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano() + int64(pid)*1e12))
|
|
execOptsNoCollide := *fuzzer.execOpts
|
|
execOptsNoCollide.Flags &= ^ipc.FlagCollide
|
|
execOptsCover := execOptsNoCollide
|
|
execOptsCover.Flags |= ipc.FlagCollectCover
|
|
execOptsComps := execOptsNoCollide
|
|
execOptsComps.Flags |= ipc.FlagCollectComps
|
|
proc := &Proc{
|
|
fuzzer: fuzzer,
|
|
pid: pid,
|
|
env: env,
|
|
rnd: rnd,
|
|
execOpts: fuzzer.execOpts,
|
|
execOptsCover: &execOptsCover,
|
|
execOptsComps: &execOptsComps,
|
|
execOptsNoCollide: &execOptsNoCollide,
|
|
}
|
|
return proc, nil
|
|
}
|
|
|
|
func (proc *Proc) loop() {
|
|
generatePeriod := 100
|
|
if proc.fuzzer.config.Flags&ipc.FlagSignal == 0 {
|
|
// If we don't have real coverage signal, generate programs more frequently
|
|
// because fallback signal is weak.
|
|
generatePeriod = 2
|
|
}
|
|
for i := 0; ; i++ {
|
|
item := proc.fuzzer.workQueue.dequeue()
|
|
if item != nil {
|
|
switch item := item.(type) {
|
|
case *WorkTriage:
|
|
proc.triageInput(item)
|
|
case *WorkCandidate:
|
|
proc.execute(proc.execOpts, item.p, item.flags, StatCandidate)
|
|
case *WorkSmash:
|
|
proc.smashInput(item)
|
|
default:
|
|
log.Fatalf("unknown work type: %#v", item)
|
|
}
|
|
continue
|
|
}
|
|
|
|
ct := proc.fuzzer.choiceTable
|
|
fuzzerSnapshot := proc.fuzzer.snapshot()
|
|
if len(fuzzerSnapshot.corpus) == 0 || i%generatePeriod == 0 {
|
|
// Generate a new prog.
|
|
p := proc.fuzzer.target.Generate(proc.rnd, prog.RecommendedCalls, ct)
|
|
log.Logf(1, "#%v: generated", proc.pid)
|
|
proc.execute(proc.execOpts, p, ProgNormal, StatGenerate)
|
|
} else {
|
|
// Mutate an existing prog.
|
|
p := fuzzerSnapshot.chooseProgram(proc.rnd).Clone()
|
|
p.Mutate(proc.rnd, prog.RecommendedCalls, ct, fuzzerSnapshot.corpus)
|
|
log.Logf(1, "#%v: mutated", proc.pid)
|
|
proc.execute(proc.execOpts, p, ProgNormal, StatFuzz)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (proc *Proc) triageInput(item *WorkTriage) {
|
|
log.Logf(1, "#%v: triaging type=%x", proc.pid, item.flags)
|
|
|
|
prio := signalPrio(item.p, &item.info, item.call)
|
|
inputSignal := signal.FromRaw(item.info.Signal, prio)
|
|
newSignal := proc.fuzzer.corpusSignalDiff(inputSignal)
|
|
if newSignal.Empty() {
|
|
return
|
|
}
|
|
callName := ".extra"
|
|
logCallName := "extra"
|
|
if item.call != -1 {
|
|
callName = item.p.Calls[item.call].Meta.Name
|
|
logCallName = fmt.Sprintf("call #%v %v", item.call, callName)
|
|
}
|
|
log.Logf(3, "triaging input for %v (new signal=%v)", logCallName, newSignal.Len())
|
|
var inputCover cover.Cover
|
|
const (
|
|
signalRuns = 3
|
|
minimizeAttempts = 3
|
|
)
|
|
// Compute input coverage and non-flaky signal for minimization.
|
|
notexecuted := 0
|
|
for i := 0; i < signalRuns; i++ {
|
|
info := proc.executeRaw(proc.execOptsCover, item.p, StatTriage)
|
|
if !reexecutionSuccess(info, &item.info, item.call) {
|
|
// The call was not executed or failed.
|
|
notexecuted++
|
|
if notexecuted > signalRuns/2+1 {
|
|
return // if happens too often, give up
|
|
}
|
|
continue
|
|
}
|
|
thisSignal, thisCover := getSignalAndCover(item.p, info, item.call)
|
|
newSignal = newSignal.Intersection(thisSignal)
|
|
// Without !minimized check manager starts losing some considerable amount
|
|
// of coverage after each restart. Mechanics of this are not completely clear.
|
|
if newSignal.Empty() && item.flags&ProgMinimized == 0 {
|
|
return
|
|
}
|
|
inputCover.Merge(thisCover)
|
|
}
|
|
if item.flags&ProgMinimized == 0 {
|
|
item.p, item.call = prog.Minimize(item.p, item.call, false,
|
|
func(p1 *prog.Prog, call1 int) bool {
|
|
for i := 0; i < minimizeAttempts; i++ {
|
|
info := proc.execute(proc.execOptsNoCollide, p1, ProgNormal, StatMinimize)
|
|
if !reexecutionSuccess(info, &item.info, call1) {
|
|
// The call was not executed or failed.
|
|
continue
|
|
}
|
|
thisSignal, _ := getSignalAndCover(p1, info, call1)
|
|
if newSignal.Intersection(thisSignal).Len() == newSignal.Len() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
})
|
|
}
|
|
|
|
data := item.p.Serialize()
|
|
sig := hash.Hash(data)
|
|
|
|
log.Logf(2, "added new input for %v to corpus:\n%s", logCallName, data)
|
|
proc.fuzzer.sendInputToManager(rpctype.RPCInput{
|
|
Call: callName,
|
|
Prog: data,
|
|
Signal: inputSignal.Serialize(),
|
|
Cover: inputCover.Serialize(),
|
|
})
|
|
|
|
proc.fuzzer.addInputToCorpus(item.p, inputSignal, sig)
|
|
|
|
if item.flags&ProgSmashed == 0 {
|
|
proc.fuzzer.workQueue.enqueue(&WorkSmash{item.p, item.call})
|
|
}
|
|
}
|
|
|
|
func reexecutionSuccess(info *ipc.ProgInfo, oldInfo *ipc.CallInfo, call int) bool {
|
|
if info == nil || len(info.Calls) == 0 {
|
|
return false
|
|
}
|
|
if call != -1 {
|
|
// Don't minimize calls from successful to unsuccessful.
|
|
// Successful calls are much more valuable.
|
|
if oldInfo.Errno == 0 && info.Calls[call].Errno != 0 {
|
|
return false
|
|
}
|
|
return len(info.Calls[call].Signal) != 0
|
|
}
|
|
return len(info.Extra.Signal) != 0
|
|
}
|
|
|
|
func getSignalAndCover(p *prog.Prog, info *ipc.ProgInfo, call int) (signal.Signal, []uint32) {
|
|
inf := &info.Extra
|
|
if call != -1 {
|
|
inf = &info.Calls[call]
|
|
}
|
|
return signal.FromRaw(inf.Signal, signalPrio(p, inf, call)), inf.Cover
|
|
}
|
|
|
|
func (proc *Proc) smashInput(item *WorkSmash) {
|
|
if proc.fuzzer.faultInjectionEnabled && item.call != -1 {
|
|
proc.failCall(item.p, item.call)
|
|
}
|
|
if proc.fuzzer.comparisonTracingEnabled && item.call != -1 {
|
|
proc.executeHintSeed(item.p, item.call)
|
|
}
|
|
fuzzerSnapshot := proc.fuzzer.snapshot()
|
|
for i := 0; i < 100; i++ {
|
|
p := item.p.Clone()
|
|
p.Mutate(proc.rnd, prog.RecommendedCalls, proc.fuzzer.choiceTable, fuzzerSnapshot.corpus)
|
|
log.Logf(1, "#%v: smash mutated", proc.pid)
|
|
proc.execute(proc.execOpts, p, ProgNormal, StatSmash)
|
|
}
|
|
}
|
|
|
|
func (proc *Proc) failCall(p *prog.Prog, call int) {
|
|
for nth := 0; nth < 100; nth++ {
|
|
log.Logf(1, "#%v: injecting fault into call %v/%v", proc.pid, call, nth)
|
|
opts := *proc.execOpts
|
|
opts.Flags |= ipc.FlagInjectFault
|
|
opts.FaultCall = call
|
|
opts.FaultNth = nth
|
|
info := proc.executeRaw(&opts, p, StatSmash)
|
|
if info != nil && len(info.Calls) > call && info.Calls[call].Flags&ipc.CallFaultInjected == 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (proc *Proc) executeHintSeed(p *prog.Prog, call int) {
|
|
log.Logf(1, "#%v: collecting comparisons", proc.pid)
|
|
// First execute the original program to dump comparisons from KCOV.
|
|
info := proc.execute(proc.execOptsComps, p, ProgNormal, StatSeed)
|
|
if info == nil {
|
|
return
|
|
}
|
|
|
|
// Then mutate the initial program for every match between
|
|
// a syscall argument and a comparison operand.
|
|
// Execute each of such mutants to check if it gives new coverage.
|
|
p.MutateWithHints(call, info.Calls[call].Comps, func(p *prog.Prog) {
|
|
log.Logf(1, "#%v: executing comparison hint", proc.pid)
|
|
proc.execute(proc.execOpts, p, ProgNormal, StatHint)
|
|
})
|
|
}
|
|
|
|
func (proc *Proc) execute(execOpts *ipc.ExecOpts, p *prog.Prog, flags ProgTypes, stat Stat) *ipc.ProgInfo {
|
|
info := proc.executeRaw(execOpts, p, stat)
|
|
calls, extra := proc.fuzzer.checkNewSignal(p, info)
|
|
for _, callIndex := range calls {
|
|
proc.enqueueCallTriage(p, flags, callIndex, info.Calls[callIndex])
|
|
}
|
|
if extra {
|
|
proc.enqueueCallTriage(p, flags, -1, info.Extra)
|
|
}
|
|
return info
|
|
}
|
|
|
|
func (proc *Proc) enqueueCallTriage(p *prog.Prog, flags ProgTypes, callIndex int, info ipc.CallInfo) {
|
|
// info.Signal points to the output shmem region, detach it before queueing.
|
|
info.Signal = append([]uint32{}, info.Signal...)
|
|
// None of the caller use Cover, so just nil it instead of detaching.
|
|
// Note: triage input uses executeRaw to get coverage.
|
|
info.Cover = nil
|
|
proc.fuzzer.workQueue.enqueue(&WorkTriage{
|
|
p: p.Clone(),
|
|
call: callIndex,
|
|
info: info,
|
|
flags: flags,
|
|
})
|
|
}
|
|
|
|
func (proc *Proc) executeRaw(opts *ipc.ExecOpts, p *prog.Prog, stat Stat) *ipc.ProgInfo {
|
|
if opts.Flags&ipc.FlagDedupCover == 0 {
|
|
log.Fatalf("dedup cover is not enabled")
|
|
}
|
|
for _, call := range p.Calls {
|
|
if !proc.fuzzer.choiceTable.Enabled(call.Meta.ID) {
|
|
panic(fmt.Sprintf("executing disabled syscall %v", call.Meta.Name))
|
|
}
|
|
}
|
|
|
|
// Limit concurrency window and do leak checking once in a while.
|
|
ticket := proc.fuzzer.gate.Enter()
|
|
defer proc.fuzzer.gate.Leave(ticket)
|
|
|
|
proc.logProgram(opts, p)
|
|
for try := 0; ; try++ {
|
|
atomic.AddUint64(&proc.fuzzer.stats[stat], 1)
|
|
output, info, hanged, err := proc.env.Exec(opts, p)
|
|
if err != nil {
|
|
if try > 10 {
|
|
log.Fatalf("executor %v failed %v times:\n%v", proc.pid, try, err)
|
|
}
|
|
log.Logf(4, "fuzzer detected executor failure='%v', retrying #%d", err, try+1)
|
|
debug.FreeOSMemory()
|
|
time.Sleep(time.Second)
|
|
continue
|
|
}
|
|
log.Logf(2, "result hanged=%v: %s", hanged, output)
|
|
return info
|
|
}
|
|
}
|
|
|
|
func (proc *Proc) logProgram(opts *ipc.ExecOpts, p *prog.Prog) {
|
|
if proc.fuzzer.outputType == OutputNone {
|
|
return
|
|
}
|
|
|
|
data := p.Serialize()
|
|
strOpts := ""
|
|
if opts.Flags&ipc.FlagInjectFault != 0 {
|
|
strOpts = fmt.Sprintf(" (fault-call:%v fault-nth:%v)", opts.FaultCall, opts.FaultNth)
|
|
}
|
|
|
|
// The following output helps to understand what program crashed kernel.
|
|
// It must not be intermixed.
|
|
switch proc.fuzzer.outputType {
|
|
case OutputStdout:
|
|
now := time.Now()
|
|
proc.fuzzer.logMu.Lock()
|
|
fmt.Printf("%02v:%02v:%02v executing program %v%v:\n%s\n",
|
|
now.Hour(), now.Minute(), now.Second(),
|
|
proc.pid, strOpts, data)
|
|
proc.fuzzer.logMu.Unlock()
|
|
case OutputDmesg:
|
|
fd, err := syscall.Open("/dev/kmsg", syscall.O_WRONLY, 0)
|
|
if err == nil {
|
|
buf := new(bytes.Buffer)
|
|
fmt.Fprintf(buf, "syzkaller: executing program %v%v:\n%s\n",
|
|
proc.pid, strOpts, data)
|
|
syscall.Write(fd, buf.Bytes())
|
|
syscall.Close(fd)
|
|
}
|
|
case OutputFile:
|
|
f, err := os.Create(fmt.Sprintf("%v-%v.prog", proc.fuzzer.name, proc.pid))
|
|
if err == nil {
|
|
if strOpts != "" {
|
|
fmt.Fprintf(f, "#%v\n", strOpts)
|
|
}
|
|
f.Write(data)
|
|
f.Close()
|
|
}
|
|
default:
|
|
log.Fatalf("unknown output type: %v", proc.fuzzer.outputType)
|
|
}
|
|
}
|