Home | History | Annotate | Download | only in syz-fuzzer
      1 // Copyright 2015 syzkaller project authors. All rights reserved.
      2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
      3 
      4 package main
      5 
      6 import (
      7 	"flag"
      8 	"net/http"
      9 	_ "net/http/pprof"
     10 	"os"
     11 	"runtime"
     12 	"runtime/debug"
     13 	"sync"
     14 	"sync/atomic"
     15 	"time"
     16 
     17 	"github.com/google/syzkaller/pkg/hash"
     18 	"github.com/google/syzkaller/pkg/host"
     19 	"github.com/google/syzkaller/pkg/ipc"
     20 	"github.com/google/syzkaller/pkg/ipc/ipcconfig"
     21 	"github.com/google/syzkaller/pkg/log"
     22 	"github.com/google/syzkaller/pkg/osutil"
     23 	"github.com/google/syzkaller/pkg/rpctype"
     24 	"github.com/google/syzkaller/pkg/signal"
     25 	"github.com/google/syzkaller/prog"
     26 	_ "github.com/google/syzkaller/sys"
     27 )
     28 
     29 type Fuzzer struct {
     30 	name        string
     31 	outputType  OutputType
     32 	config      *ipc.Config
     33 	execOpts    *ipc.ExecOpts
     34 	procs       []*Proc
     35 	gate        *ipc.Gate
     36 	workQueue   *WorkQueue
     37 	needPoll    chan struct{}
     38 	choiceTable *prog.ChoiceTable
     39 	stats       [StatCount]uint64
     40 	manager     *rpctype.RPCClient
     41 	target      *prog.Target
     42 
     43 	faultInjectionEnabled    bool
     44 	comparisonTracingEnabled bool
     45 
     46 	corpusMu     sync.RWMutex
     47 	corpus       []*prog.Prog
     48 	corpusHashes map[hash.Sig]struct{}
     49 
     50 	signalMu     sync.RWMutex
     51 	corpusSignal signal.Signal // signal of inputs in corpus
     52 	maxSignal    signal.Signal // max signal ever observed including flakes
     53 	newSignal    signal.Signal // diff of maxSignal since last sync with master
     54 
     55 	logMu sync.Mutex
     56 }
     57 
     58 type Stat int
     59 
     60 const (
     61 	StatGenerate Stat = iota
     62 	StatFuzz
     63 	StatCandidate
     64 	StatTriage
     65 	StatMinimize
     66 	StatSmash
     67 	StatHint
     68 	StatSeed
     69 	StatCount
     70 )
     71 
     72 var statNames = [StatCount]string{
     73 	StatGenerate:  "exec gen",
     74 	StatFuzz:      "exec fuzz",
     75 	StatCandidate: "exec candidate",
     76 	StatTriage:    "exec triage",
     77 	StatMinimize:  "exec minimize",
     78 	StatSmash:     "exec smash",
     79 	StatHint:      "exec hints",
     80 	StatSeed:      "exec seeds",
     81 }
     82 
     83 type OutputType int
     84 
     85 const (
     86 	OutputNone OutputType = iota
     87 	OutputStdout
     88 	OutputDmesg
     89 	OutputFile
     90 )
     91 
     92 func main() {
     93 	debug.SetGCPercent(50)
     94 
     95 	var (
     96 		flagName    = flag.String("name", "test", "unique name for manager")
     97 		flagOS      = flag.String("os", runtime.GOOS, "target OS")
     98 		flagArch    = flag.String("arch", runtime.GOARCH, "target arch")
     99 		flagManager = flag.String("manager", "", "manager rpc address")
    100 		flagProcs   = flag.Int("procs", 1, "number of parallel test processes")
    101 		flagOutput  = flag.String("output", "stdout", "write programs to none/stdout/dmesg/file")
    102 		flagPprof   = flag.String("pprof", "", "address to serve pprof profiles")
    103 		flagTest    = flag.Bool("test", false, "enable image testing mode")      // used by syz-ci
    104 		flagRunTest = flag.Bool("runtest", false, "enable program testing mode") // used by pkg/runtest
    105 	)
    106 	flag.Parse()
    107 	outputType := parseOutputType(*flagOutput)
    108 	log.Logf(0, "fuzzer started")
    109 
    110 	target, err := prog.GetTarget(*flagOS, *flagArch)
    111 	if err != nil {
    112 		log.Fatalf("%v", err)
    113 	}
    114 
    115 	config, execOpts, err := ipcconfig.Default(target)
    116 	if err != nil {
    117 		log.Fatalf("failed to create default ipc config: %v", err)
    118 	}
    119 	sandbox := "none"
    120 	if config.Flags&ipc.FlagSandboxSetuid != 0 {
    121 		sandbox = "setuid"
    122 	} else if config.Flags&ipc.FlagSandboxNamespace != 0 {
    123 		sandbox = "namespace"
    124 	}
    125 
    126 	shutdown := make(chan struct{})
    127 	osutil.HandleInterrupts(shutdown)
    128 	go func() {
    129 		// Handles graceful preemption on GCE.
    130 		<-shutdown
    131 		log.Logf(0, "SYZ-FUZZER: PREEMPTED")
    132 		os.Exit(1)
    133 	}()
    134 
    135 	checkArgs := &checkArgs{
    136 		target:      target,
    137 		sandbox:     sandbox,
    138 		ipcConfig:   config,
    139 		ipcExecOpts: execOpts,
    140 	}
    141 	if *flagTest {
    142 		testImage(*flagManager, checkArgs)
    143 		return
    144 	}
    145 
    146 	if *flagPprof != "" {
    147 		go func() {
    148 			err := http.ListenAndServe(*flagPprof, nil)
    149 			log.Fatalf("failed to serve pprof profiles: %v", err)
    150 		}()
    151 	} else {
    152 		runtime.MemProfileRate = 0
    153 	}
    154 
    155 	log.Logf(0, "dialing manager at %v", *flagManager)
    156 	manager, err := rpctype.NewRPCClient(*flagManager)
    157 	if err != nil {
    158 		log.Fatalf("failed to connect to manager: %v ", err)
    159 	}
    160 	a := &rpctype.ConnectArgs{Name: *flagName}
    161 	r := &rpctype.ConnectRes{}
    162 	if err := manager.Call("Manager.Connect", a, r); err != nil {
    163 		log.Fatalf("failed to connect to manager: %v ", err)
    164 	}
    165 	if r.CheckResult == nil {
    166 		checkArgs.gitRevision = r.GitRevision
    167 		checkArgs.targetRevision = r.TargetRevision
    168 		checkArgs.enabledCalls = r.EnabledCalls
    169 		checkArgs.allSandboxes = r.AllSandboxes
    170 		r.CheckResult, err = checkMachine(checkArgs)
    171 		if err != nil {
    172 			r.CheckResult = &rpctype.CheckArgs{
    173 				Error: err.Error(),
    174 			}
    175 		}
    176 		r.CheckResult.Name = *flagName
    177 		if err := manager.Call("Manager.Check", r.CheckResult, nil); err != nil {
    178 			log.Fatalf("Manager.Check call failed: %v", err)
    179 		}
    180 		if r.CheckResult.Error != "" {
    181 			log.Fatalf("%v", r.CheckResult.Error)
    182 		}
    183 	}
    184 	log.Logf(0, "syscalls: %v", len(r.CheckResult.EnabledCalls))
    185 	for _, feat := range r.CheckResult.Features {
    186 		log.Logf(0, "%v: %v", feat.Name, feat.Reason)
    187 	}
    188 	periodicCallback, err := host.Setup(target, r.CheckResult.Features)
    189 	if err != nil {
    190 		log.Fatalf("BUG: %v", err)
    191 	}
    192 	if r.CheckResult.Features[host.FeatureNetworkInjection].Enabled {
    193 		config.Flags |= ipc.FlagEnableTun
    194 	}
    195 	if r.CheckResult.Features[host.FeatureNetworkDevices].Enabled {
    196 		config.Flags |= ipc.FlagEnableNetDev
    197 	}
    198 	if r.CheckResult.Features[host.FeatureFaultInjection].Enabled {
    199 		config.Flags |= ipc.FlagEnableFault
    200 	}
    201 
    202 	if *flagRunTest {
    203 		runTest(target, manager, *flagName, config.Executor)
    204 		return
    205 	}
    206 
    207 	needPoll := make(chan struct{}, 1)
    208 	needPoll <- struct{}{}
    209 	fuzzer := &Fuzzer{
    210 		name:                     *flagName,
    211 		outputType:               outputType,
    212 		config:                   config,
    213 		execOpts:                 execOpts,
    214 		gate:                     ipc.NewGate(2**flagProcs, periodicCallback),
    215 		workQueue:                newWorkQueue(*flagProcs, needPoll),
    216 		needPoll:                 needPoll,
    217 		manager:                  manager,
    218 		target:                   target,
    219 		faultInjectionEnabled:    r.CheckResult.Features[host.FeatureFaultInjection].Enabled,
    220 		comparisonTracingEnabled: r.CheckResult.Features[host.FeatureComparisons].Enabled,
    221 		corpusHashes:             make(map[hash.Sig]struct{}),
    222 	}
    223 	for i := 0; fuzzer.poll(i == 0, nil); i++ {
    224 	}
    225 	calls := make(map[*prog.Syscall]bool)
    226 	for _, id := range r.CheckResult.EnabledCalls[sandbox] {
    227 		calls[target.Syscalls[id]] = true
    228 	}
    229 	prios := target.CalculatePriorities(fuzzer.corpus)
    230 	fuzzer.choiceTable = target.BuildChoiceTable(prios, calls)
    231 
    232 	for pid := 0; pid < *flagProcs; pid++ {
    233 		proc, err := newProc(fuzzer, pid)
    234 		if err != nil {
    235 			log.Fatalf("failed to create proc: %v", err)
    236 		}
    237 		fuzzer.procs = append(fuzzer.procs, proc)
    238 		go proc.loop()
    239 	}
    240 
    241 	fuzzer.pollLoop()
    242 }
    243 
    244 func (fuzzer *Fuzzer) pollLoop() {
    245 	var execTotal uint64
    246 	var lastPoll time.Time
    247 	var lastPrint time.Time
    248 	ticker := time.NewTicker(3 * time.Second).C
    249 	for {
    250 		poll := false
    251 		select {
    252 		case <-ticker:
    253 		case <-fuzzer.needPoll:
    254 			poll = true
    255 		}
    256 		if fuzzer.outputType != OutputStdout && time.Since(lastPrint) > 10*time.Second {
    257 			// Keep-alive for manager.
    258 			log.Logf(0, "alive, executed %v", execTotal)
    259 			lastPrint = time.Now()
    260 		}
    261 		if poll || time.Since(lastPoll) > 10*time.Second {
    262 			needCandidates := fuzzer.workQueue.wantCandidates()
    263 			if poll && !needCandidates {
    264 				continue
    265 			}
    266 			stats := make(map[string]uint64)
    267 			for _, proc := range fuzzer.procs {
    268 				stats["exec total"] += atomic.SwapUint64(&proc.env.StatExecs, 0)
    269 				stats["executor restarts"] += atomic.SwapUint64(&proc.env.StatRestarts, 0)
    270 			}
    271 			for stat := Stat(0); stat < StatCount; stat++ {
    272 				v := atomic.SwapUint64(&fuzzer.stats[stat], 0)
    273 				stats[statNames[stat]] = v
    274 				execTotal += v
    275 			}
    276 			if !fuzzer.poll(needCandidates, stats) {
    277 				lastPoll = time.Now()
    278 			}
    279 		}
    280 	}
    281 }
    282 
    283 func (fuzzer *Fuzzer) poll(needCandidates bool, stats map[string]uint64) bool {
    284 	a := &rpctype.PollArgs{
    285 		Name:           fuzzer.name,
    286 		NeedCandidates: needCandidates,
    287 		MaxSignal:      fuzzer.grabNewSignal().Serialize(),
    288 		Stats:          stats,
    289 	}
    290 	r := &rpctype.PollRes{}
    291 	if err := fuzzer.manager.Call("Manager.Poll", a, r); err != nil {
    292 		log.Fatalf("Manager.Poll call failed: %v", err)
    293 	}
    294 	maxSignal := r.MaxSignal.Deserialize()
    295 	log.Logf(1, "poll: candidates=%v inputs=%v signal=%v",
    296 		len(r.Candidates), len(r.NewInputs), maxSignal.Len())
    297 	fuzzer.addMaxSignal(maxSignal)
    298 	for _, inp := range r.NewInputs {
    299 		fuzzer.addInputFromAnotherFuzzer(inp)
    300 	}
    301 	for _, candidate := range r.Candidates {
    302 		p, err := fuzzer.target.Deserialize(candidate.Prog)
    303 		if err != nil {
    304 			log.Fatalf("failed to parse program from manager: %v", err)
    305 		}
    306 		flags := ProgCandidate
    307 		if candidate.Minimized {
    308 			flags |= ProgMinimized
    309 		}
    310 		if candidate.Smashed {
    311 			flags |= ProgSmashed
    312 		}
    313 		fuzzer.workQueue.enqueue(&WorkCandidate{
    314 			p:     p,
    315 			flags: flags,
    316 		})
    317 	}
    318 	return len(r.NewInputs) != 0 || len(r.Candidates) != 0 || maxSignal.Len() != 0
    319 }
    320 
    321 func (fuzzer *Fuzzer) sendInputToManager(inp rpctype.RPCInput) {
    322 	a := &rpctype.NewInputArgs{
    323 		Name:     fuzzer.name,
    324 		RPCInput: inp,
    325 	}
    326 	if err := fuzzer.manager.Call("Manager.NewInput", a, nil); err != nil {
    327 		log.Fatalf("Manager.NewInput call failed: %v", err)
    328 	}
    329 }
    330 
    331 func (fuzzer *Fuzzer) addInputFromAnotherFuzzer(inp rpctype.RPCInput) {
    332 	p, err := fuzzer.target.Deserialize(inp.Prog)
    333 	if err != nil {
    334 		log.Fatalf("failed to deserialize prog from another fuzzer: %v", err)
    335 	}
    336 	sig := hash.Hash(inp.Prog)
    337 	sign := inp.Signal.Deserialize()
    338 	fuzzer.addInputToCorpus(p, sign, sig)
    339 }
    340 
    341 func (fuzzer *Fuzzer) addInputToCorpus(p *prog.Prog, sign signal.Signal, sig hash.Sig) {
    342 	fuzzer.corpusMu.Lock()
    343 	if _, ok := fuzzer.corpusHashes[sig]; !ok {
    344 		fuzzer.corpus = append(fuzzer.corpus, p)
    345 		fuzzer.corpusHashes[sig] = struct{}{}
    346 	}
    347 	fuzzer.corpusMu.Unlock()
    348 
    349 	if !sign.Empty() {
    350 		fuzzer.signalMu.Lock()
    351 		fuzzer.corpusSignal.Merge(sign)
    352 		fuzzer.maxSignal.Merge(sign)
    353 		fuzzer.signalMu.Unlock()
    354 	}
    355 }
    356 
    357 func (fuzzer *Fuzzer) corpusSnapshot() []*prog.Prog {
    358 	fuzzer.corpusMu.RLock()
    359 	defer fuzzer.corpusMu.RUnlock()
    360 	return fuzzer.corpus
    361 }
    362 
    363 func (fuzzer *Fuzzer) addMaxSignal(sign signal.Signal) {
    364 	if sign.Len() == 0 {
    365 		return
    366 	}
    367 	fuzzer.signalMu.Lock()
    368 	defer fuzzer.signalMu.Unlock()
    369 	fuzzer.maxSignal.Merge(sign)
    370 }
    371 
    372 func (fuzzer *Fuzzer) grabNewSignal() signal.Signal {
    373 	fuzzer.signalMu.Lock()
    374 	defer fuzzer.signalMu.Unlock()
    375 	sign := fuzzer.newSignal
    376 	if sign.Empty() {
    377 		return nil
    378 	}
    379 	fuzzer.newSignal = nil
    380 	return sign
    381 }
    382 
    383 func (fuzzer *Fuzzer) corpusSignalDiff(sign signal.Signal) signal.Signal {
    384 	fuzzer.signalMu.RLock()
    385 	defer fuzzer.signalMu.RUnlock()
    386 	return fuzzer.corpusSignal.Diff(sign)
    387 }
    388 
    389 func (fuzzer *Fuzzer) checkNewSignal(p *prog.Prog, info []ipc.CallInfo) (calls []int) {
    390 	fuzzer.signalMu.RLock()
    391 	defer fuzzer.signalMu.RUnlock()
    392 	for i, inf := range info {
    393 		diff := fuzzer.maxSignal.DiffRaw(inf.Signal, signalPrio(p.Target, p.Calls[i], &inf))
    394 		if diff.Empty() {
    395 			continue
    396 		}
    397 		calls = append(calls, i)
    398 		fuzzer.signalMu.RUnlock()
    399 		fuzzer.signalMu.Lock()
    400 		fuzzer.maxSignal.Merge(diff)
    401 		fuzzer.newSignal.Merge(diff)
    402 		fuzzer.signalMu.Unlock()
    403 		fuzzer.signalMu.RLock()
    404 	}
    405 	return
    406 }
    407 
    408 func signalPrio(target *prog.Target, c *prog.Call, ci *ipc.CallInfo) (prio uint8) {
    409 	if ci.Errno == 0 {
    410 		prio |= 1 << 1
    411 	}
    412 	if !target.CallContainsAny(c) {
    413 		prio |= 1 << 0
    414 	}
    415 	return
    416 }
    417 
    418 func parseOutputType(str string) OutputType {
    419 	switch str {
    420 	case "none":
    421 		return OutputNone
    422 	case "stdout":
    423 		return OutputStdout
    424 	case "dmesg":
    425 		return OutputDmesg
    426 	case "file":
    427 		return OutputFile
    428 	default:
    429 		log.Fatalf("-output flag must be one of none/stdout/dmesg/file")
    430 		return OutputNone
    431 	}
    432 }
    433