tools/syz-reprolist: add utility to extract list of reproducers

This commit is contained in:
Dmitry Vyukov 2019-10-07 17:50:24 +02:00
parent 0f33548a39
commit 64612bfd3c
3 changed files with 328 additions and 0 deletions

View File

@ -49,6 +49,8 @@ var apiNamespaceHandlers = map[string]APINamespaceHandler{
"manager_stats": apiManagerStats,
"commit_poll": apiCommitPoll,
"upload_commits": apiUploadCommits,
"bug_list": apiBugList,
"load_bug": apiLoadBug,
}
type JSONHandler func(c context.Context, r *http.Request) (interface{}, error)
@ -971,6 +973,71 @@ func apiManagerStats(c context.Context, ns string, r *http.Request, payload []by
return nil, err
}
func apiBugList(c context.Context, ns string, r *http.Request, payload []byte) (interface{}, error) {
keys, err := db.NewQuery("Bug").
Filter("Namespace=", ns).
KeysOnly().
GetAll(c, nil)
if err != nil {
return nil, fmt.Errorf("failed to query bugs: %v", err)
}
resp := &dashapi.BugListResp{}
for _, key := range keys {
resp.List = append(resp.List, key.StringID())
}
return resp, nil
}
func apiLoadBug(c context.Context, ns string, r *http.Request, payload []byte) (interface{}, error) {
req := new(dashapi.LoadBugReq)
if err := json.Unmarshal(payload, req); err != nil {
return nil, fmt.Errorf("failed to unmarshal request: %v", err)
}
bug := new(Bug)
bugKey := db.NewKey(c, "Bug", req.ID, 0, nil)
if err := db.Get(c, bugKey, bug); err != nil {
return nil, fmt.Errorf("failed to get bug: %v", err)
}
if bug.Namespace != ns {
return nil, fmt.Errorf("no such bug")
}
if bug.sanitizeAccess(AccessPublic) > AccessPublic {
return nil, nil
}
crash, _, err := findCrashForBug(c, bug)
if err != nil {
return nil, err
}
build, err := loadBuild(c, ns, crash.BuildID)
if err != nil {
return nil, err
}
reproSyz, _, err := getText(c, textReproSyz, crash.ReproSyz)
if err != nil {
return nil, err
}
reproC, _, err := getText(c, textReproC, crash.ReproC)
if err != nil {
return nil, err
}
statuses := map[int]string{
BugStatusOpen: "open",
BugStatusFixed: "fixed",
BugStatusInvalid: "invalid",
BugStatusDup: "dup",
}
resp := &dashapi.LoadBugResp{
ID: req.ID,
Title: bug.displayTitle(),
Status: statuses[bug.Status],
SyzkallerCommit: build.SyzkallerCommit,
ReproOpts: crash.ReproOpts,
ReproSyz: reproSyz,
ReproC: reproC,
}
return resp, nil
}
func findBugForCrash(c context.Context, ns, title string) (*Bug, *db.Key, error) {
var bugs []*Bug
keys, err := db.NewQuery("Bug").

View File

@ -460,6 +460,38 @@ func (dash *Dashboard) UploadManagerStats(req *ManagerStatsReq) error {
return dash.Query("manager_stats", req, nil)
}
type BugListResp struct {
List []string
}
func (dash *Dashboard) BugList() (*BugListResp, error) {
resp := new(BugListResp)
err := dash.Query("bug_list", nil, resp)
return resp, err
}
type LoadBugReq struct {
ID string
}
type LoadBugResp struct {
ID string
Title string
Status string
SyzkallerCommit string
Arch string
ReproOpts []byte
ReproSyz []byte
ReproC []byte
}
func (dash *Dashboard) LoadBug(id string) (*LoadBugResp, error) {
req := LoadBugReq{id}
resp := new(LoadBugResp)
err := dash.Query("load_bug", req, resp)
return resp, err
}
type (
BugStatus int
BugNotif int

View File

@ -0,0 +1,229 @@
// 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
}