| // Copyright 2019 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 ( |
| "flag" |
| "fmt" |
| "io/ioutil" |
| "log" |
| "os" |
| "path/filepath" |
| "runtime" |
| "strings" |
| "sync" |
| "time" |
| |
| "github.com/google/syzkaller/dashboard/dashapi" |
| "github.com/google/syzkaller/pkg/csource" |
| "github.com/google/syzkaller/pkg/osutil" |
| "github.com/google/syzkaller/pkg/vcs" |
| ) |
| |
| var ( |
| flagDashboard = flag.String("dashboard", "https://syzkaller.appspot.com", "dashboard address") |
| flagAPIClient = flag.String("client", "", "api client") |
| flagAPIKey = flag.String("key", "", "api key") |
| flagOutputDir = flag.String("output", "repros", "output dir") |
| flagSyzkallerDir = flag.String("syzkaller", ".", "syzkaller dir") |
| flagOS = flag.String("os", runtime.GOOS, "target OS") |
| ) |
| |
| func main() { |
| flag.Parse() |
| if *flagAPIClient == "" || *flagAPIKey == "" { |
| log.Fatalf("api client and key are required") |
| } |
| if err := os.MkdirAll(*flagOutputDir, 0755); err != nil { |
| log.Fatalf("failed to create output dir: %v", err) |
| } |
| dash := dashapi.New(*flagAPIClient, *flagDashboard, *flagAPIKey) |
| resp, err := dash.BugList() |
| if err != nil { |
| log.Fatalf("api call failed: %v", err) |
| } |
| log.Printf("loading %v bugs", len(resp.List)) |
| const P = 10 |
| idchan := make(chan string, 10*P) |
| bugchan := make(chan *dashapi.LoadBugResp, 10*P) |
| go func() { |
| for _, id := range resp.List { |
| if _, err := os.Stat(filepath.Join(*flagOutputDir, id+".c")); err == nil { |
| log.Printf("%v: already present", id) |
| continue |
| } |
| if _, err := os.Stat(filepath.Join(*flagOutputDir, id+".norepro")); err == nil { |
| log.Printf("%v: no repro (cached)", id) |
| continue |
| } |
| idchan <- id |
| } |
| close(idchan) |
| }() |
| var wg sync.WaitGroup |
| wg.Add(P) |
| for p := 0; p < P; p++ { |
| go func() { |
| defer wg.Done() |
| for id := range idchan { |
| resp, err := dash.LoadBug(id) |
| if err != nil { |
| log.Printf("%v: failed to load bug: %v", id, err) |
| continue |
| } |
| if resp.ID == "" { |
| continue |
| } |
| bugchan <- resp |
| } |
| }() |
| } |
| go func() { |
| wg.Wait() |
| close(bugchan) |
| }() |
| writeRepros(bugchan) |
| } |
| |
| func writeRepros(bugchan chan *dashapi.LoadBugResp) { |
| for bug := range bugchan { |
| if len(bug.ReproSyz) == 0 { |
| log.Printf("%v: %v: no repro", bug.ID, bug.Status) |
| file := filepath.Join(*flagOutputDir, bug.ID+".norepro") |
| if err := ioutil.WriteFile(file, nil, 0644); err != nil { |
| log.Fatalf("failed to write file: %v", err) |
| } |
| continue |
| } |
| if len(bug.ReproC) == 0 { |
| log.Printf("%v: %v: syz repro on %v", bug.ID, bug.Status, bug.SyzkallerCommit) |
| if err := createCRepro(bug); err != nil { |
| log.Print(err) |
| continue |
| } |
| } |
| log.Printf("%v: %v: C repro", bug.ID, bug.Status) |
| arch := "" |
| if bug.Arch != "" && bug.Arch != "amd64" { |
| arch = fmt.Sprintf(" arch:%v", bug.Arch) |
| } |
| repro := []byte(fmt.Sprintf("// %v\n// %v/bug?id=%v\n// status:%v%v\n", |
| bug.Title, *flagDashboard, bug.ID, bug.Status, arch)) |
| repro = append(repro, bug.ReproC...) |
| file := filepath.Join(*flagOutputDir, bug.ID+".c") |
| if err := ioutil.WriteFile(file, repro, 0644); err != nil { |
| log.Fatalf("failed to write file: %v", err) |
| } |
| } |
| } |
| |
| func createCRepro(bug *dashapi.LoadBugResp) error { |
| opts, err := csource.DeserializeOptions(bug.ReproOpts) |
| if err != nil { |
| return fmt.Errorf("failed to deserialize opts: %v", err) |
| } |
| file := filepath.Join(*flagOutputDir, bug.ID+".syz") |
| if err := ioutil.WriteFile(file, bug.ReproSyz, 0644); err != nil { |
| return fmt.Errorf("failed to write file: %v", err) |
| } |
| repo := vcs.NewSyzkallerRepo(*flagSyzkallerDir) |
| if _, err := repo.SwitchCommit(bug.SyzkallerCommit); err != nil { |
| return fmt.Errorf("failed to checkout commit %v: %v", bug.SyzkallerCommit, err) |
| } |
| if _, err := osutil.RunCmd(time.Hour, *flagSyzkallerDir, "make", "prog2c"); err != nil { |
| return err |
| } |
| bin := filepath.Join(*flagSyzkallerDir, "bin", "syz-prog2c") |
| args := createProg2CArgs(bug, opts, file) |
| output, err := osutil.RunCmd(time.Hour, "", bin, args...) |
| if err != nil { |
| return err |
| } |
| bug.ReproC = output |
| return err |
| } |
| |
| func createProg2CArgs(bug *dashapi.LoadBugResp, opts csource.Options, file string) []string { |
| haveEnableFlag := containsCommit("dfd609eca1871f01757d6b04b19fc273c87c14e5") |
| haveRepeatFlag := containsCommit("b25fc7b83119e8dca728a199fd92e24dd4c33fa4") |
| haveCgroupFlag := containsCommit("9753d3be5e6c79e271ed128795039f161ee339b7") |
| haveWaitRepeatFlag := containsCommit("c99b02d2248fbdcd6f44037326b16c928f4423f1") |
| haveWaitRepeatRemoved := containsCommit("9fe4bdc5f1037a409e82299f36117030114c7b94") |
| haveCloseFds := containsCommit("5c51045d28eb1ad9465a51487d436133ce7b98d2") |
| haveOSFlag := containsCommit("aa2533b98d21ebcad5777310215159127bfe3573") |
| args := []string{ |
| "-prog", file, |
| "-sandbox", opts.Sandbox, |
| fmt.Sprintf("-segv=%v", opts.HandleSegv), |
| fmt.Sprintf("-collide=%v", opts.Collide), |
| fmt.Sprintf("-threaded=%v", opts.Threaded), |
| } |
| if haveOSFlag { |
| args = append(args, "-os", *flagOS) |
| } |
| if bug.Arch != "" && bug.Arch != "amd64" { |
| args = append(args, "-arch", bug.Arch) |
| } |
| if opts.Fault { |
| args = append(args, []string{ |
| fmt.Sprintf("-fault_call=%v", opts.FaultCall), |
| fmt.Sprintf("-fault_nth=%v", opts.FaultNth), |
| }...) |
| } |
| if opts.Repeat { |
| if haveRepeatFlag { |
| args = append(args, fmt.Sprintf("-repeat=%v", opts.RepeatTimes)) |
| } else { |
| args = append(args, "-repeat") |
| } |
| } |
| if opts.Procs > 0 { |
| args = append(args, fmt.Sprintf("-procs=%v", opts.Procs)) |
| } |
| if opts.UseTmpDir { |
| args = append(args, "-tmpdir") |
| } |
| if opts.Leak { |
| args = append(args, "-leak") |
| } |
| var enable, flags []string |
| if opts.EnableTun { |
| enable = append(enable, "tun") |
| flags = append(flags, "-tun") |
| } |
| if opts.EnableNetDev { |
| enable = append(enable, "net_dev") |
| flags = append(flags, "-netdev") |
| } |
| if opts.EnableNetReset { |
| enable = append(enable, "net_reset") |
| flags = append(flags, "-resetnet") |
| } |
| if opts.EnableCgroups { |
| enable = append(enable, "cgroups") |
| if haveCgroupFlag { |
| flags = append(flags, "-cgroups") |
| if haveWaitRepeatFlag && !haveWaitRepeatRemoved { |
| flags = append(flags, "-waitrepeat") |
| } |
| } |
| } |
| if opts.EnableBinfmtMisc { |
| enable = append(enable, "binfmt_misc") |
| } |
| if opts.EnableCloseFds && haveCloseFds { |
| enable = append(enable, "close_fds") |
| } |
| if !haveEnableFlag { |
| args = append(args, flags...) |
| } else if len(enable) != 0 { |
| args = append(args, "-enable", strings.Join(enable, ",")) |
| } |
| return args |
| } |
| |
| func containsCommit(hash string) bool { |
| _, err := osutil.RunCmd(time.Hour, *flagSyzkallerDir, "git", "merge-base", "--is-ancestor", hash, "HEAD") |
| return err == nil |
| } |