github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/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 "strings" 17 "sync" 18 "sync/atomic" 19 "time" 20 21 "github.com/google/syzkaller/pkg/cover/backend" 22 "github.com/google/syzkaller/pkg/csource" 23 "github.com/google/syzkaller/pkg/db" 24 "github.com/google/syzkaller/pkg/flatrpc" 25 "github.com/google/syzkaller/pkg/fuzzer/queue" 26 "github.com/google/syzkaller/pkg/log" 27 "github.com/google/syzkaller/pkg/mgrconfig" 28 "github.com/google/syzkaller/pkg/osutil" 29 "github.com/google/syzkaller/pkg/rpcserver" 30 "github.com/google/syzkaller/pkg/tool" 31 "github.com/google/syzkaller/pkg/vminfo" 32 "github.com/google/syzkaller/prog" 33 _ "github.com/google/syzkaller/sys" 34 "github.com/google/syzkaller/sys/targets" 35 ) 36 37 var ( 38 flagOS = flag.String("os", runtime.GOOS, "target os") 39 flagArch = flag.String("arch", runtime.GOARCH, "target arch") 40 flagType = flag.String("type", "", "target VM type") 41 flagCoverFile = flag.String("coverfile", "", "write coverage to the file") 42 flagRepeat = flag.Int("repeat", 1, "repeat execution that many times (0 for infinite loop)") 43 flagProcs = flag.Int("procs", 2*runtime.NumCPU(), "number of parallel processes to execute programs") 44 flagOutput = flag.Bool("output", false, "write programs and results to stdout") 45 flagHints = flag.Bool("hints", false, "do a hints-generation run") 46 flagEnable = flag.String("enable", "none", "enable only listed additional features") 47 flagDisable = flag.String("disable", "none", "enable all additional features except listed") 48 flagExecutor = flag.String("executor", "./syz-executor", "path to executor binary") 49 flagThreaded = flag.Bool("threaded", true, "use threaded mode in executor") 50 flagSignal = flag.Bool("cover", false, "collect feedback signals (coverage)") 51 flagSandbox = flag.String("sandbox", "none", "sandbox for fuzzing (none/setuid/namespace/android)") 52 flagSandboxArg = flag.Int("sandbox_arg", 0, "argument for sandbox runner to adjust it via config") 53 flagDebug = flag.Bool("debug", false, "debug output from executor") 54 flagSlowdown = flag.Int("slowdown", 1, "execution slowdown caused by emulation/instrumentation") 55 flagUnsafe = flag.Bool("unsafe", false, "use unsafe program deserialization mode") 56 flagGlob = flag.String("glob", "", "run glob expansion request") 57 58 // The in the stress mode resembles simple unguided fuzzer. 59 // This mode can be used as an intermediate step when porting syzkaller to a new OS, 60 // or when testing on a machine that is not supported by the vm package (as syz-manager cannot be used). 61 // To use this mode one needs to start a VM manually, copy syz-execprog and run it. 62 // syz-execprog will execute random programs infinitely until it's stopped or it crashes 63 // the kernel underneath. If it's given a corpus of programs, it will alternate between 64 // executing random programs and mutated programs from the corpus. 65 flagStress = flag.Bool("stress", false, "enable stress mode (local fuzzer)") 66 flagSyscalls = flag.String("syscalls", "", "comma-separated list of enabled syscalls for the stress mode") 67 68 flagGDB = flag.Bool("gdb", false, "start executor under gdb") 69 70 // The following flag is only kept to let syzkaller remain compatible with older execprog versions. 71 // In order to test incoming patches or perform bug bisection, syz-ci must use the exact syzkaller 72 // version that detected the bug (as descriptions and syntax could've already been changed), and 73 // therefore it must be able to invoke older versions of syz-execprog. 74 // Unfortunately there's no clean way to drop that flag from newer versions of syz-execprog. If it 75 // were false by default, it would be easy - we could modify `instance.ExecprogCmd` only to pass it 76 // when it's true - which would never be the case in the newer versions (this is how we got rid of 77 // fault injection args). But the collide flag was true by default, so it must be passed by value 78 // (-collide=%v). The least kludgy solution is to silently accept this flag also in the newer versions 79 // of syzkaller, but do not process it, as there's no such functionality anymore. 80 // Note, however, that we do not have to do the same for `syz-prog2c`, as `collide` was there false 81 // by default. 82 _ = flag.Bool("collide", false, "(DEPRECATED) collide syscalls to provoke data races") 83 ) 84 85 func main() { 86 flag.Usage = func() { 87 fmt.Fprintf(os.Stderr, "usage: execprog [flags] file-with-programs-or-corpus.db+\n") 88 flag.PrintDefaults() 89 csource.PrintAvailableFeaturesFlags() 90 } 91 defer tool.Init()() 92 target, err := prog.GetTarget(*flagOS, *flagArch) 93 if err != nil { 94 tool.Fail(err) 95 } 96 97 featureFlags, err := csource.ParseFeaturesFlags(*flagEnable, *flagDisable, true) 98 if err != nil { 99 log.Fatalf("%v", err) 100 } 101 features := flatrpc.AllFeatures 102 for feat := range flatrpc.EnumNamesFeature { 103 opt := csource.FlatRPCFeaturesToCSource[feat] 104 if opt != "" && !featureFlags[opt].Enabled { 105 features &= ^feat 106 } 107 } 108 109 var requestedSyscalls []int 110 if *flagStress { 111 syscallList := strings.Split(*flagSyscalls, ",") 112 if *flagSyscalls == "" { 113 syscallList = nil 114 } 115 requestedSyscalls, err = mgrconfig.ParseEnabledSyscalls(target, syscallList, nil, mgrconfig.AnyDescriptions) 116 if err != nil { 117 tool.Failf("failed to parse enabled syscalls: %v", err) 118 } 119 } 120 121 sandbox, err := flatrpc.SandboxToFlags(*flagSandbox) 122 if err != nil { 123 tool.Failf("failed to parse sandbox: %v", err) 124 } 125 env := sandbox 126 if *flagDebug { 127 env |= flatrpc.ExecEnvDebug 128 } 129 cover := *flagSignal || *flagHints || *flagCoverFile != "" 130 if cover { 131 env |= flatrpc.ExecEnvSignal 132 } 133 var exec flatrpc.ExecFlag 134 if *flagThreaded { 135 exec |= flatrpc.ExecFlagThreaded 136 } 137 if *flagCoverFile == "" { 138 exec |= flatrpc.ExecFlagDedupCover 139 } 140 141 progs := loadPrograms(target, flag.Args()) 142 if *flagGlob == "" && !*flagStress && len(progs) == 0 { 143 flag.Usage() 144 os.Exit(1) 145 } 146 rpcCtx, done := context.WithCancel(context.Background()) 147 ctx := &Context{ 148 target: target, 149 done: done, 150 progs: progs, 151 globs: strings.Split(*flagGlob, ":"), 152 rs: rand.NewSource(time.Now().UnixNano()), 153 coverFile: *flagCoverFile, 154 output: *flagOutput, 155 signal: *flagSignal, 156 hints: *flagHints, 157 stress: *flagStress, 158 repeat: *flagRepeat, 159 defaultOpts: flatrpc.ExecOpts{ 160 EnvFlags: env, 161 ExecFlags: exec, 162 SandboxArg: int64(*flagSandboxArg), 163 }, 164 } 165 166 cfg := &rpcserver.LocalConfig{ 167 Config: rpcserver.Config{ 168 Config: vminfo.Config{ 169 Target: target, 170 VMType: *flagType, 171 Features: features, 172 Syscalls: requestedSyscalls, 173 Debug: *flagDebug, 174 Cover: cover, 175 Sandbox: sandbox, 176 SandboxArg: int64(*flagSandboxArg), 177 }, 178 Procs: *flagProcs, 179 Slowdown: *flagSlowdown, 180 }, 181 Executor: *flagExecutor, 182 HandleInterrupts: true, 183 GDB: *flagGDB, 184 MachineChecked: ctx.machineChecked, 185 OutputWriter: os.Stderr, 186 } 187 if err := rpcserver.RunLocal(rpcCtx, cfg); err != nil { 188 tool.Fail(err) 189 } 190 } 191 192 type Context struct { 193 target *prog.Target 194 done func() 195 progs []*prog.Prog 196 globs []string 197 defaultOpts flatrpc.ExecOpts 198 choiceTable *prog.ChoiceTable 199 logMu sync.Mutex 200 posMu sync.Mutex 201 rs rand.Source 202 coverFile string 203 output bool 204 signal bool 205 hints bool 206 stress bool 207 repeat int 208 pos int 209 completed atomic.Uint64 210 resultIndex atomic.Int64 211 lastPrint time.Time 212 } 213 214 func (ctx *Context) machineChecked(features flatrpc.Feature, syscalls map[*prog.Syscall]bool) queue.Source { 215 if ctx.stress { 216 ctx.choiceTable = ctx.target.BuildChoiceTable(ctx.progs, syscalls) 217 } 218 ctx.defaultOpts.EnvFlags |= csource.FeaturesToFlags(features, nil) 219 return queue.DefaultOpts(ctx, ctx.defaultOpts) 220 } 221 222 func (ctx *Context) Next() *queue.Request { 223 if *flagGlob != "" { 224 idx := int(ctx.resultIndex.Add(1) - 1) 225 if idx >= len(ctx.globs) { 226 return nil 227 } 228 req := &queue.Request{ 229 Type: flatrpc.RequestTypeGlob, 230 GlobPattern: ctx.globs[idx], 231 } 232 req.OnDone(ctx.doneGlob) 233 return req 234 } 235 var p *prog.Prog 236 if ctx.stress { 237 p = ctx.createStressProg() 238 } else { 239 idx := ctx.getProgramIndex() 240 if idx < 0 { 241 return nil 242 } 243 p = ctx.progs[idx] 244 } 245 if ctx.output { 246 data := p.Serialize() 247 ctx.logMu.Lock() 248 log.Logf(0, "executing program:\n%s", data) 249 ctx.logMu.Unlock() 250 } 251 252 req := &queue.Request{ 253 Prog: p, 254 } 255 if ctx.hints { 256 req.ExecOpts.ExecFlags |= flatrpc.ExecFlagCollectComps 257 } else if ctx.signal || ctx.coverFile != "" { 258 req.ExecOpts.ExecFlags |= flatrpc.ExecFlagCollectSignal | flatrpc.ExecFlagCollectCover 259 } 260 req.OnDone(ctx.Done) 261 return req 262 } 263 264 func (ctx *Context) doneGlob(req *queue.Request, res *queue.Result) bool { 265 if res.Status == queue.Success { 266 files := res.GlobFiles() 267 ctx.logMu.Lock() 268 fmt.Printf("glob %q expanded to %v files\n", req.GlobPattern, len(files)) 269 for _, file := range files { 270 fmt.Printf("\t%q\n", file) 271 } 272 ctx.logMu.Unlock() 273 } else { 274 fmt.Printf("request failed: %v (%v)\n%s\n", res.Status, res.Err, res.Output) 275 } 276 completed := int(ctx.completed.Add(1)) 277 if completed >= len(ctx.globs) { 278 ctx.done() 279 } 280 return true 281 } 282 283 func (ctx *Context) Done(req *queue.Request, res *queue.Result) bool { 284 if res.Info != nil { 285 ctx.printCallResults(res.Info) 286 if ctx.hints { 287 ctx.printHints(req.Prog, res.Info) 288 } 289 if ctx.coverFile != "" { 290 ctx.dumpCoverage(res.Info) 291 } 292 } 293 completed := int(ctx.completed.Add(1)) 294 if ctx.repeat > 0 && completed >= len(ctx.progs)*ctx.repeat { 295 ctx.done() 296 } 297 return true 298 } 299 300 func (ctx *Context) printCallResults(info *flatrpc.ProgInfo) { 301 for i, inf := range info.Calls { 302 if inf.Flags&flatrpc.CallFlagExecuted == 0 { 303 continue 304 } 305 flags := "" 306 if inf.Flags&flatrpc.CallFlagFinished == 0 { 307 flags += " unfinished" 308 } 309 if inf.Flags&flatrpc.CallFlagBlocked != 0 { 310 flags += " blocked" 311 } 312 if inf.Flags&flatrpc.CallFlagFaultInjected != 0 { 313 flags += " faulted" 314 } 315 log.Logf(1, "CALL %v: signal %v, coverage %v errno %v%v", 316 i, len(inf.Signal), len(inf.Cover), inf.Error, flags) 317 } 318 } 319 320 func (ctx *Context) printHints(p *prog.Prog, info *flatrpc.ProgInfo) { 321 ncomps, ncandidates := 0, 0 322 for i := range p.Calls { 323 if ctx.output { 324 fmt.Printf("call %v:\n", i) 325 } 326 comps := make(prog.CompMap) 327 for _, cmp := range info.Calls[i].Comps { 328 comps.Add(cmp.Pc, cmp.Op1, cmp.Op2, cmp.IsConst) 329 if ctx.output { 330 fmt.Printf("comp 0x%x ? 0x%x\n", cmp.Op1, cmp.Op2) 331 } 332 } 333 ncomps += len(comps) 334 p.MutateWithHints(i, comps, func(p *prog.Prog) bool { 335 ncandidates++ 336 if ctx.output { 337 log.Logf(1, "PROGRAM:\n%s", p.Serialize()) 338 } 339 return true 340 }) 341 } 342 log.Logf(0, "ncomps=%v ncandidates=%v", ncomps, ncandidates) 343 } 344 345 func (ctx *Context) dumpCallCoverage(coverFile string, info *flatrpc.CallInfo) { 346 if info == nil || len(info.Cover) == 0 { 347 return 348 } 349 sysTarget := targets.Get(ctx.target.OS, ctx.target.Arch) 350 buf := new(bytes.Buffer) 351 for _, pc := range info.Cover { 352 prev := backend.PreviousInstructionPC(sysTarget, "", pc) 353 fmt.Fprintf(buf, "0x%x\n", prev) 354 } 355 err := osutil.WriteFile(coverFile, buf.Bytes()) 356 if err != nil { 357 log.Fatalf("failed to write coverage file: %v", err) 358 } 359 } 360 361 func (ctx *Context) dumpCoverage(info *flatrpc.ProgInfo) { 362 coverFile := fmt.Sprintf("%s_prog%v", ctx.coverFile, ctx.resultIndex.Add(1)) 363 for i, inf := range info.Calls { 364 log.Logf(0, "call #%v: signal %v, coverage %v", i, len(inf.Signal), len(inf.Cover)) 365 ctx.dumpCallCoverage(fmt.Sprintf("%v.%v", coverFile, i), inf) 366 } 367 if info.Extra != nil { 368 log.Logf(0, "extra: signal %v, coverage %v", len(info.Extra.Signal), len(info.Extra.Cover)) 369 ctx.dumpCallCoverage(fmt.Sprintf("%v.extra", coverFile), info.Extra) 370 } 371 } 372 373 func (ctx *Context) getProgramIndex() int { 374 ctx.posMu.Lock() 375 defer ctx.posMu.Unlock() 376 if ctx.repeat > 0 && ctx.pos >= len(ctx.progs)*ctx.repeat { 377 return -1 378 } 379 idx := ctx.pos % len(ctx.progs) 380 if idx == 0 && time.Since(ctx.lastPrint) > 5*time.Second { 381 log.Logf(0, "executed programs: %v", ctx.pos) 382 ctx.lastPrint = time.Now() 383 } 384 ctx.pos++ 385 return idx 386 } 387 388 func (ctx *Context) createStressProg() *prog.Prog { 389 ctx.posMu.Lock() 390 rnd := rand.New(ctx.rs) 391 ctx.posMu.Unlock() 392 if len(ctx.progs) == 0 || rnd.Intn(2) == 0 { 393 return ctx.target.Generate(rnd, prog.RecommendedCalls, ctx.choiceTable) 394 } 395 p := ctx.progs[rnd.Intn(len(ctx.progs))].Clone() 396 p.Mutate(rnd, prog.RecommendedCalls, ctx.choiceTable, nil, ctx.progs) 397 return p 398 } 399 400 func loadPrograms(target *prog.Target, files []string) []*prog.Prog { 401 var progs []*prog.Prog 402 mode := prog.NonStrict 403 if *flagUnsafe { 404 mode = prog.NonStrictUnsafe 405 } 406 for _, fn := range files { 407 if corpus, err := db.Open(fn, false); err == nil { 408 for _, rec := range corpus.Records { 409 p, err := target.Deserialize(rec.Val, mode) 410 if err != nil { 411 continue 412 } 413 progs = append(progs, p) 414 } 415 continue 416 } 417 data, err := os.ReadFile(fn) 418 if err != nil { 419 log.Fatalf("failed to read log file: %v", err) 420 } 421 for _, entry := range target.ParseLog(data, mode) { 422 progs = append(progs, entry.P) 423 } 424 } 425 log.Logf(0, "parsed %v programs", len(progs)) 426 return progs 427 }