github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/tools/syz-execprog/execprog.go (about) 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 // execprog executes a single program or a set of programs 5 // and optionally prints information about execution. 6 package main 7 8 import ( 9 "bytes" 10 "context" 11 "flag" 12 "fmt" 13 "math/rand" 14 "os" 15 "runtime" 16 "strconv" 17 "strings" 18 "sync" 19 "time" 20 21 "github.com/google/syzkaller/pkg/cover" 22 "github.com/google/syzkaller/pkg/cover/backend" 23 "github.com/google/syzkaller/pkg/csource" 24 "github.com/google/syzkaller/pkg/db" 25 "github.com/google/syzkaller/pkg/flatrpc" 26 "github.com/google/syzkaller/pkg/fuzzer/queue" 27 "github.com/google/syzkaller/pkg/host" 28 "github.com/google/syzkaller/pkg/ipc" 29 "github.com/google/syzkaller/pkg/ipc/ipcconfig" 30 "github.com/google/syzkaller/pkg/log" 31 "github.com/google/syzkaller/pkg/mgrconfig" 32 "github.com/google/syzkaller/pkg/osutil" 33 "github.com/google/syzkaller/pkg/tool" 34 "github.com/google/syzkaller/pkg/vminfo" 35 "github.com/google/syzkaller/prog" 36 _ "github.com/google/syzkaller/sys" 37 "github.com/google/syzkaller/sys/targets" 38 ) 39 40 var ( 41 flagOS = flag.String("os", runtime.GOOS, "target os") 42 flagArch = flag.String("arch", runtime.GOARCH, "target arch") 43 flagCoverFile = flag.String("coverfile", "", "write coverage to the file") 44 flagRepeat = flag.Int("repeat", 1, "repeat execution that many times (0 for infinite loop)") 45 flagProcs = flag.Int("procs", 2*runtime.NumCPU(), "number of parallel processes to execute programs") 46 flagOutput = flag.Bool("output", false, "write programs and results to stdout") 47 flagHints = flag.Bool("hints", false, "do a hints-generation run") 48 flagEnable = flag.String("enable", "none", "enable only listed additional features") 49 flagDisable = flag.String("disable", "none", "enable all additional features except listed") 50 51 // The in the stress mode resembles simple unguided fuzzer. 52 // This mode can be used as an intermediate step when porting syzkaller to a new OS, 53 // or when testing on a machine that is not supported by the vm package (as syz-manager cannot be used). 54 // To use this mode one needs to start a VM manually, copy syz-execprog and run it. 55 // syz-execprog will execute random programs infinitely until it's stopped or it crashes 56 // the kernel underneath. If it's given a corpus of programs, it will alternate between 57 // executing random programs and mutated programs from the corpus. 58 flagStress = flag.Bool("stress", false, "enable stress mode (local fuzzer)") 59 flagSyscalls = flag.String("syscalls", "", "comma-separated list of enabled syscalls for the stress mode") 60 61 // The following flag is only kept to let syzkaller remain compatible with older execprog versions. 62 // In order to test incoming patches or perform bug bisection, syz-ci must use the exact syzkaller 63 // version that detected the bug (as descriptions and syntax could've already been changed), and 64 // therefore it must be able to invoke older versions of syz-execprog. 65 // Unfortunately there's no clean way to drop that flag from newer versions of syz-execprog. If it 66 // were false by default, it would be easy - we could modify `instance.ExecprogCmd` only to pass it 67 // when it's true - which would never be the case in the newer versions (this is how we got rid of 68 // fault injection args). But the collide flag was true by default, so it must be passed by value 69 // (-collide=%v). The least kludgy solution is to silently accept this flag also in the newer versions 70 // of syzkaller, but do not process it, as there's no such functionality anymore. 71 // Note, however, that we do not have to do the same for `syz-prog2c`, as `collide` was there false 72 // by default. 73 flagCollide = flag.Bool("collide", false, "(DEPRECATED) collide syscalls to provoke data races") 74 ) 75 76 func main() { 77 flag.Usage = func() { 78 fmt.Fprintf(os.Stderr, "usage: execprog [flags] file-with-programs-or-corpus.db+\n") 79 flag.PrintDefaults() 80 csource.PrintAvailableFeaturesFlags() 81 } 82 defer tool.Init()() 83 featuresFlags, err := csource.ParseFeaturesFlags(*flagEnable, *flagDisable, true) 84 if err != nil { 85 log.Fatalf("%v", err) 86 } 87 88 target, err := prog.GetTarget(*flagOS, *flagArch) 89 if err != nil { 90 log.Fatalf("%v", err) 91 } 92 93 progs := loadPrograms(target, flag.Args()) 94 if !*flagStress && len(progs) == 0 { 95 flag.Usage() 96 os.Exit(1) 97 } 98 if *flagCollide { 99 log.Logf(0, "note: setting -collide to true is deprecated now and has no effect") 100 } 101 var requestedSyscalls []int 102 if *flagStress { 103 syscallList := strings.Split(*flagSyscalls, ",") 104 if *flagSyscalls == "" { 105 syscallList = nil 106 } 107 requestedSyscalls, err = mgrconfig.ParseEnabledSyscalls(target, syscallList, nil) 108 if err != nil { 109 log.Fatalf("failed to parse enabled syscalls: %v", err) 110 } 111 } 112 config, execOpts, syscalls, features := createConfig(target, featuresFlags, requestedSyscalls) 113 var gateCallback func() 114 if features&flatrpc.FeatureLeak != 0 { 115 gateCallback = func() { 116 output, err := osutil.RunCmd(10*time.Minute, "", config.Executor, "leak") 117 if err != nil { 118 os.Stdout.Write(output) 119 os.Exit(1) 120 } 121 } 122 } 123 var choiceTable *prog.ChoiceTable 124 if *flagStress { 125 choiceTable = target.BuildChoiceTable(progs, syscalls) 126 } 127 sysTarget := targets.Get(*flagOS, *flagArch) 128 upperBase := getKernelUpperBase(sysTarget) 129 ctx := &Context{ 130 target: target, 131 progs: progs, 132 choiceTable: choiceTable, 133 config: config, 134 execOpts: execOpts, 135 gate: ipc.NewGate(2**flagProcs, gateCallback), 136 shutdown: make(chan struct{}), 137 stress: *flagStress, 138 repeat: *flagRepeat, 139 sysTarget: sysTarget, 140 upperBase: upperBase, 141 } 142 var wg sync.WaitGroup 143 wg.Add(*flagProcs) 144 for p := 0; p < *flagProcs; p++ { 145 pid := p 146 go func() { 147 defer wg.Done() 148 ctx.run(pid) 149 }() 150 } 151 osutil.HandleInterrupts(ctx.shutdown) 152 wg.Wait() 153 } 154 155 type Context struct { 156 target *prog.Target 157 progs []*prog.Prog 158 choiceTable *prog.ChoiceTable 159 config *ipc.Config 160 execOpts *ipc.ExecOpts 161 gate *ipc.Gate 162 shutdown chan struct{} 163 logMu sync.Mutex 164 posMu sync.Mutex 165 stress bool 166 repeat int 167 pos int 168 lastPrint time.Time 169 sysTarget *targets.Target 170 upperBase uint32 171 } 172 173 func (ctx *Context) run(pid int) { 174 env, err := ipc.MakeEnv(ctx.config, pid) 175 if err != nil { 176 log.Fatalf("failed to create ipc env: %v", err) 177 } 178 defer env.Close() 179 rs := rand.NewSource(time.Now().UnixNano() + int64(pid)*1e12) 180 for { 181 select { 182 case <-ctx.shutdown: 183 return 184 default: 185 } 186 if ctx.stress { 187 p := ctx.createStressProg(rs) 188 ctx.execute(pid, env, p, 0) 189 } else { 190 idx := ctx.getProgramIndex() 191 if ctx.repeat > 0 && idx >= len(ctx.progs)*ctx.repeat { 192 return 193 } 194 p := ctx.progs[idx%len(ctx.progs)] 195 ctx.execute(pid, env, p, idx) 196 } 197 } 198 } 199 200 func (ctx *Context) execute(pid int, env *ipc.Env, p *prog.Prog, progIndex int) { 201 // Limit concurrency window. 202 ticket := ctx.gate.Enter() 203 defer ctx.gate.Leave(ticket) 204 205 callOpts := ctx.execOpts 206 if *flagOutput { 207 ctx.logProgram(pid, p, callOpts) 208 } 209 progData, err := p.SerializeForExec() 210 if err != nil { 211 log.Logf(1, "RESULT: failed to serialize: %v", err) 212 return 213 } 214 // This mimics the syz-fuzzer logic. This is important for reproduction. 215 for try := 0; ; try++ { 216 output, info, hanged, err := env.ExecProg(callOpts, progData) 217 if err != nil { 218 if ctx.execOpts.EnvFlags&flatrpc.ExecEnvDebug != 0 { 219 log.Logf(0, "result: hanged=%v err=%v\n\n%s", hanged, err, output) 220 } 221 if try > 10 { 222 log.SyzFatalf("executor %d failed %d times: %v\n%s", pid, try, err, output) 223 } 224 // Don't print err/output in this case as it may contain "SYZFAIL" and we want to fail yet. 225 log.Logf(1, "executor failed, retrying") 226 if try > 3 { 227 time.Sleep(100 * time.Millisecond) 228 } 229 continue 230 } 231 if info != nil { 232 ctx.printCallResults(info) 233 if *flagHints { 234 ctx.printHints(p, info) 235 } 236 if *flagCoverFile != "" { 237 covFile := fmt.Sprintf("%s_prog%d", *flagCoverFile, progIndex) 238 ctx.dumpCoverage(covFile, info) 239 } 240 } else { 241 log.Logf(1, "RESULT: no calls executed") 242 } 243 break 244 } 245 } 246 247 func (ctx *Context) logProgram(pid int, p *prog.Prog, callOpts *ipc.ExecOpts) { 248 data := p.Serialize() 249 ctx.logMu.Lock() 250 log.Logf(0, "executing program %v:\n%s", pid, data) 251 ctx.logMu.Unlock() 252 } 253 254 func (ctx *Context) printCallResults(info *ipc.ProgInfo) { 255 for i, inf := range info.Calls { 256 if inf.Flags&ipc.CallExecuted == 0 { 257 continue 258 } 259 flags := "" 260 if inf.Flags&ipc.CallFinished == 0 { 261 flags += " unfinished" 262 } 263 if inf.Flags&ipc.CallBlocked != 0 { 264 flags += " blocked" 265 } 266 if inf.Flags&ipc.CallFaultInjected != 0 { 267 flags += " faulted" 268 } 269 log.Logf(1, "CALL %v: signal %v, coverage %v errno %v%v", 270 i, len(inf.Signal), len(inf.Cover), inf.Errno, flags) 271 } 272 } 273 274 func (ctx *Context) printHints(p *prog.Prog, info *ipc.ProgInfo) { 275 ncomps, ncandidates := 0, 0 276 for i := range p.Calls { 277 if *flagOutput { 278 fmt.Printf("call %v:\n", i) 279 } 280 comps := info.Calls[i].Comps 281 for v, args := range comps { 282 ncomps += len(args) 283 if *flagOutput { 284 fmt.Printf("comp 0x%x:", v) 285 for arg := range args { 286 fmt.Printf(" 0x%x", arg) 287 } 288 fmt.Printf("\n") 289 } 290 } 291 p.MutateWithHints(i, comps, func(p *prog.Prog) bool { 292 ncandidates++ 293 if *flagOutput { 294 log.Logf(1, "PROGRAM:\n%s", p.Serialize()) 295 } 296 return true 297 }) 298 } 299 log.Logf(0, "ncomps=%v ncandidates=%v", ncomps, ncandidates) 300 } 301 302 func getKernelUpperBase(target *targets.Target) uint32 { 303 defaultRet := uint32(0xffffffff) 304 if target.OS == targets.Linux { 305 // Read the first 8 bytes from /proc/kallsyms. 306 f, err := os.Open("/proc/kallsyms") 307 if err != nil { 308 log.Logf(1, "could not get kernel fixup address: %v", err) 309 return defaultRet 310 } 311 defer f.Close() 312 data := make([]byte, 8) 313 _, err = f.ReadAt(data, 0) 314 if err != nil { 315 log.Logf(1, "could not get kernel fixup address: %v", err) 316 return defaultRet 317 } 318 value, err := strconv.ParseUint(string(data), 16, 32) 319 if err != nil { 320 log.Logf(1, "could not get kernel fixup address: %v", err) 321 return defaultRet 322 } 323 return uint32(value) 324 } 325 return defaultRet 326 } 327 328 func (ctx *Context) dumpCallCoverage(coverFile string, info *ipc.CallInfo) { 329 if len(info.Cover) == 0 { 330 return 331 } 332 buf := new(bytes.Buffer) 333 for _, pc := range info.Cover { 334 prev := backend.PreviousInstructionPC(ctx.sysTarget, cover.RestorePC(pc, ctx.upperBase)) 335 fmt.Fprintf(buf, "0x%x\n", prev) 336 } 337 err := osutil.WriteFile(coverFile, buf.Bytes()) 338 if err != nil { 339 log.Fatalf("failed to write coverage file: %v", err) 340 } 341 } 342 343 func (ctx *Context) dumpCoverage(coverFile string, info *ipc.ProgInfo) { 344 for i, inf := range info.Calls { 345 log.Logf(0, "call #%v: signal %v, coverage %v", i, len(inf.Signal), len(inf.Cover)) 346 ctx.dumpCallCoverage(fmt.Sprintf("%v.%v", coverFile, i), &inf) 347 } 348 log.Logf(0, "extra: signal %v, coverage %v", len(info.Extra.Signal), len(info.Extra.Cover)) 349 ctx.dumpCallCoverage(fmt.Sprintf("%v.extra", coverFile), &info.Extra) 350 } 351 352 func (ctx *Context) getProgramIndex() int { 353 ctx.posMu.Lock() 354 idx := ctx.pos 355 ctx.pos++ 356 if idx%len(ctx.progs) == 0 && time.Since(ctx.lastPrint) > 5*time.Second { 357 log.Logf(0, "executed programs: %v", idx) 358 ctx.lastPrint = time.Now() 359 } 360 ctx.posMu.Unlock() 361 return idx 362 } 363 364 func (ctx *Context) createStressProg(rs rand.Source) *prog.Prog { 365 rnd := rand.New(rs) 366 if len(ctx.progs) == 0 || rnd.Intn(2) == 0 { 367 return ctx.target.Generate(rs, prog.RecommendedCalls, ctx.choiceTable) 368 } 369 p := ctx.progs[rnd.Intn(len(ctx.progs))].Clone() 370 p.Mutate(rs, prog.RecommendedCalls, ctx.choiceTable, nil, ctx.progs) 371 return p 372 } 373 374 func loadPrograms(target *prog.Target, files []string) []*prog.Prog { 375 var progs []*prog.Prog 376 for _, fn := range files { 377 if corpus, err := db.Open(fn, false); err == nil { 378 for _, rec := range corpus.Records { 379 p, err := target.Deserialize(rec.Val, prog.NonStrict) 380 if err != nil { 381 continue 382 } 383 progs = append(progs, p) 384 } 385 continue 386 } 387 data, err := os.ReadFile(fn) 388 if err != nil { 389 log.Fatalf("failed to read log file: %v", err) 390 } 391 for _, entry := range target.ParseLog(data) { 392 progs = append(progs, entry.P) 393 } 394 } 395 log.Logf(0, "parsed %v programs", len(progs)) 396 return progs 397 } 398 399 func createConfig(target *prog.Target, featuresFlags csource.Features, syscalls []int) ( 400 *ipc.Config, *ipc.ExecOpts, map[*prog.Syscall]bool, flatrpc.Feature) { 401 config, execOpts, err := ipcconfig.Default(target) 402 if err != nil { 403 log.Fatalf("%v", err) 404 } 405 if execOpts.EnvFlags&flatrpc.ExecEnvSignal != 0 { 406 execOpts.ExecFlags |= flatrpc.ExecFlagCollectCover 407 } 408 if *flagCoverFile != "" { 409 execOpts.EnvFlags |= flatrpc.ExecEnvSignal 410 execOpts.ExecFlags |= flatrpc.ExecFlagCollectCover 411 execOpts.ExecFlags &^= flatrpc.ExecFlagDedupCover 412 } 413 if *flagHints { 414 if execOpts.ExecFlags&flatrpc.ExecFlagCollectCover != 0 { 415 execOpts.ExecFlags ^= flatrpc.ExecFlagCollectCover 416 } 417 execOpts.ExecFlags |= flatrpc.ExecFlagCollectComps 418 } 419 cfg := &mgrconfig.Config{ 420 Sandbox: ipc.FlagsToSandbox(execOpts.EnvFlags), 421 SandboxArg: execOpts.SandboxArg, 422 Derived: mgrconfig.Derived{ 423 TargetOS: target.OS, 424 TargetArch: target.Arch, 425 TargetVMArch: target.Arch, 426 Target: target, 427 SysTarget: targets.Get(target.OS, target.Arch), 428 Syscalls: syscalls, 429 }, 430 } 431 checker := vminfo.New(cfg) 432 fileInfos := host.ReadFiles(checker.RequiredFiles()) 433 featureInfos, err := host.SetupFeatures(target, config.Executor, flatrpc.AllFeatures, featuresFlags) 434 if err != nil { 435 log.Fatal(err) 436 } 437 438 ctx, cancel := context.WithCancel(context.Background()) 439 defer cancel() 440 go checkerExecutor(ctx, checker, config) 441 442 enabledSyscalls, disabledSyscalls, features, err := checker.Run(fileInfos, featureInfos) 443 if err != nil { 444 log.Fatal(err) 445 } 446 if *flagOutput { 447 for feat, info := range features { 448 log.Logf(0, "%-24v: %v", flatrpc.EnumNamesFeature[feat], info.Reason) 449 } 450 for c, reason := range disabledSyscalls { 451 log.Logf(0, "unsupported syscall: %v: %v", c.Name, reason) 452 } 453 enabledSyscalls, disabledSyscalls = target.TransitivelyEnabledCalls(enabledSyscalls) 454 for c, reason := range disabledSyscalls { 455 log.Logf(0, "transitively unsupported: %v: %v", c.Name, reason) 456 } 457 } 458 execOpts.EnvFlags |= ipc.FeaturesToFlags(features.Enabled(), featuresFlags) 459 return config, execOpts, enabledSyscalls, features.Enabled() 460 } 461 462 func checkerExecutor(ctx context.Context, source queue.Source, config *ipc.Config) { 463 env, err := ipc.MakeEnv(config, 0) 464 if err != nil { 465 log.Fatalf("failed to create ipc env: %v", err) 466 } 467 defer env.Close() 468 for { 469 req := source.Next() 470 if req == nil { 471 select { 472 case <-time.After(time.Second / 100): 473 case <-ctx.Done(): 474 return 475 } 476 continue 477 } 478 progData, err := req.Prog.SerializeForExec() 479 if err != nil { 480 log.Fatalf("failed to serialize %s: %v", req.Prog.Serialize(), err) 481 } 482 output, info, hanged, err := env.ExecProg(&req.ExecOpts, progData) 483 res := &queue.Result{ 484 Status: queue.Success, 485 Info: info, 486 Output: output, 487 Err: err, 488 } 489 if err != nil { 490 res.Status = queue.ExecFailure 491 } 492 if hanged && err == nil { 493 res.Status = queue.ExecFailure 494 res.Err = fmt.Errorf("hanged") 495 } 496 req.Done(res) 497 } 498 }