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