// Copyright 2015 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 manager import ( "bytes" "encoding/json" "fmt" "html/template" "io" "net/http" _ "net/http/pprof" "os" "path/filepath" "regexp" "runtime/debug" "sort" "strconv" "strings" "sync/atomic" "time" "github.com/google/syzkaller/pkg/corpus" "github.com/google/syzkaller/pkg/cover" "github.com/google/syzkaller/pkg/fuzzer" "github.com/google/syzkaller/pkg/html/pages" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/mgrconfig" "github.com/google/syzkaller/pkg/stat" "github.com/google/syzkaller/pkg/vcs" "github.com/google/syzkaller/pkg/vminfo" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/vm" "github.com/google/syzkaller/vm/dispatcher" "github.com/gorilla/handlers" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) type CoverageInfo struct { Modules []*vminfo.KernelModule ReportGenerator *ReportGeneratorWrapper CoverFilter map[uint64]struct{} } type HTTPServer struct { // To be set once. Cfg *mgrconfig.Config StartTime time.Time Corpus *corpus.Corpus CrashStore *CrashStore // Set dynamically. Fuzzer atomic.Pointer[fuzzer.Fuzzer] Cover atomic.Pointer[CoverageInfo] ReproLoop atomic.Pointer[ReproLoop] Pool atomic.Pointer[dispatcher.Pool[*vm.Instance]] EnabledSyscalls atomic.Value // map[*prog.Syscall]bool // Internal state. expertMode bool } func (serv *HTTPServer) Serve() { handle := func(pattern string, handler func(http.ResponseWriter, *http.Request)) { http.Handle(pattern, handlers.CompressHandler(http.HandlerFunc(handler))) } handle("/", serv.httpSummary) handle("/config", serv.httpConfig) handle("/expert_mode", serv.httpExpertMode) handle("/stats", serv.httpStats) handle("/vms", serv.httpVMs) handle("/vm", serv.httpVM) handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}).ServeHTTP) handle("/syscalls", serv.httpSyscalls) handle("/corpus", serv.httpCorpus) handle("/corpus.db", serv.httpDownloadCorpus) handle("/crash", serv.httpCrash) handle("/cover", serv.httpCover) handle("/subsystemcover", serv.httpSubsystemCover) handle("/modulecover", serv.httpModuleCover) handle("/prio", serv.httpPrio) handle("/file", serv.httpFile) handle("/report", serv.httpReport) handle("/rawcover", serv.httpRawCover) handle("/rawcoverfiles", serv.httpRawCoverFiles) handle("/filterpcs", serv.httpFilterPCs) handle("/funccover", serv.httpFuncCover) handle("/filecover", serv.httpFileCover) handle("/input", serv.httpInput) handle("/debuginput", serv.httpDebugInput) handle("/modules", serv.modulesInfo) handle("/jobs", serv.httpJobs) // Browsers like to request this, without special handler this goes to / handler. handle("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {}) log.Logf(0, "serving http on http://%v", serv.Cfg.HTTP) err := http.ListenAndServe(serv.Cfg.HTTP, nil) if err != nil { log.Fatalf("failed to listen on %v: %v", serv.Cfg.HTTP, err) } } func (serv *HTTPServer) httpSummary(w http.ResponseWriter, r *http.Request) { revision, link := revisionAndLink() data := &UISummaryData{ Name: serv.Cfg.Name, Revision: revision, RevisionLink: link, Expert: serv.expertMode, Log: log.CachedLogOutput(), } level := stat.Simple if serv.expertMode { level = stat.All } for _, stat := range stat.Collect(level) { data.Stats = append(data.Stats, UIStat{ Name: stat.Name, Value: stat.Value, Hint: stat.Desc, Link: stat.Link, }) } var err error if data.Crashes, err = serv.collectCrashes(serv.Cfg.Workdir); err != nil { http.Error(w, fmt.Sprintf("failed to collect crashes: %v", err), http.StatusInternalServerError) return } executeTemplate(w, summaryTemplate, data) } func revisionAndLink() (string, string) { var revision string var link string if len(prog.GitRevisionBase) > 8 { revision = prog.GitRevisionBase[:8] link = vcs.LogLink(vcs.SyzkallerRepo, prog.GitRevisionBase) } else { revision = prog.GitRevisionBase link = "" } return revision, link } func (serv *HTTPServer) httpConfig(w http.ResponseWriter, r *http.Request) { data, err := json.MarshalIndent(serv.Cfg, "", "\t") if err != nil { http.Error(w, fmt.Sprintf("failed to encode json: %v", err), http.StatusInternalServerError) return } w.Write(data) } func (serv *HTTPServer) httpExpertMode(w http.ResponseWriter, r *http.Request) { serv.expertMode = !serv.expertMode http.Redirect(w, r, "/", http.StatusFound) } func (serv *HTTPServer) httpSyscalls(w http.ResponseWriter, r *http.Request) { var calls map[string]*corpus.CallCov if obj := serv.EnabledSyscalls.Load(); obj != nil { calls = serv.Corpus.CallCover() // Add enabled, but not yet covered calls. for call := range obj.(map[*prog.Syscall]bool) { if calls[call.Name] == nil { calls[call.Name] = new(corpus.CallCov) } } } data := &UISyscallsData{ Name: serv.Cfg.Name, } for c, cc := range calls { var syscallID *int if syscall, ok := serv.Cfg.Target.SyscallMap[c]; ok { syscallID = &syscall.ID } data.Calls = append(data.Calls, UICallType{ Name: c, ID: syscallID, Inputs: cc.Count, Cover: len(cc.Cover), }) } sort.Slice(data.Calls, func(i, j int) bool { return data.Calls[i].Name < data.Calls[j].Name }) executeTemplate(w, syscallsTemplate, data) } func (serv *HTTPServer) httpStats(w http.ResponseWriter, r *http.Request) { executeTemplate(w, pages.StatsTemplate, stat.RenderGraphs()) } func (serv *HTTPServer) httpVMs(w http.ResponseWriter, r *http.Request) { pool := serv.Pool.Load() if pool == nil { http.Error(w, "no VM pool is present (yet)", http.StatusInternalServerError) return } data := &UIVMData{ Name: serv.Cfg.Name, } // TODO: we could also query vmLoop for VMs that are idle (waiting to start reproducing), // and query the exact bug that is being reproduced by a VM. for id, state := range pool.State() { name := fmt.Sprintf("#%d", id) info := UIVMInfo{ Name: name, State: "unknown", Since: time.Since(state.LastUpdate), } switch state.State { case dispatcher.StateOffline: info.State = "offline" case dispatcher.StateBooting: info.State = "booting" case dispatcher.StateWaiting: info.State = "waiting" case dispatcher.StateRunning: info.State = "running: " + state.Status } if state.Reserved { info.State = "[reserved] " + info.State } if state.MachineInfo != nil { info.MachineInfo = fmt.Sprintf("/vm?type=machine-info&id=%d", id) } if state.DetailedStatus != nil { info.DetailedStatus = fmt.Sprintf("/vm?type=detailed-status&id=%v", id) } data.VMs = append(data.VMs, info) } executeTemplate(w, vmsTemplate, data) } func (serv *HTTPServer) httpVM(w http.ResponseWriter, r *http.Request) { pool := serv.Pool.Load() if pool == nil { http.Error(w, "no VM pool is present (yet)", http.StatusInternalServerError) return } w.Header().Set("Content-Type", ctTextPlain) id, err := strconv.Atoi(r.FormValue("id")) infos := pool.State() if err != nil || id < 0 || id >= len(infos) { http.Error(w, "invalid instance id", http.StatusBadRequest) return } info := infos[id] switch r.FormValue("type") { case "machine-info": if info.MachineInfo != nil { w.Write(info.MachineInfo()) } case "detailed-status": if info.DetailedStatus != nil { w.Write(info.DetailedStatus()) } default: w.Write([]byte("unknown info type")) } } func makeUICrashType(info *BugInfo, startTime time.Time, repros map[string]bool) *UICrashType { var crashes []*UICrash for _, crash := range info.Crashes { crashes = append(crashes, &UICrash{ CrashInfo: crash, Active: crash.Time.After(startTime), }) } triaged := reproStatus(info.HasRepro, info.HasCRepro, repros[info.Title], info.ReproAttempts >= MaxReproAttempts) return &UICrashType{ Description: info.Title, LastTime: info.LastTime, Active: info.LastTime.After(startTime), ID: info.ID, Count: len(info.Crashes), Triaged: triaged, Strace: info.StraceFile, Crashes: crashes, } } var crashIDRe = regexp.MustCompile(`^\w+$`) func (serv *HTTPServer) httpCrash(w http.ResponseWriter, r *http.Request) { crashID := r.FormValue("id") if !crashIDRe.MatchString(crashID) { http.Error(w, "invalid crash ID", http.StatusBadRequest) return } info, err := serv.CrashStore.BugInfo(crashID, true) if err != nil { http.Error(w, "failed to read crash info", http.StatusInternalServerError) return } crash := makeUICrashType(info, serv.StartTime, nil) executeTemplate(w, crashTemplate, crash) } func (serv *HTTPServer) httpCorpus(w http.ResponseWriter, r *http.Request) { data := UICorpus{ Call: r.FormValue("call"), RawCover: serv.Cfg.RawCover, } for _, inp := range serv.Corpus.Items() { if data.Call != "" && data.Call != inp.StringCall() { continue } data.Inputs = append(data.Inputs, &UIInput{ Sig: inp.Sig, Short: inp.Prog.String(), Cover: len(inp.Cover), }) } sort.Slice(data.Inputs, func(i, j int) bool { a, b := data.Inputs[i], data.Inputs[j] if a.Cover != b.Cover { return a.Cover > b.Cover } return a.Short < b.Short }) executeTemplate(w, corpusTemplate, data) } func (serv *HTTPServer) httpDownloadCorpus(w http.ResponseWriter, r *http.Request) { corpus := filepath.Join(serv.Cfg.Workdir, "corpus.db") file, err := os.Open(corpus) if err != nil { http.Error(w, fmt.Sprintf("failed to open corpus : %v", err), http.StatusInternalServerError) return } defer file.Close() buf, err := io.ReadAll(file) if err != nil { http.Error(w, fmt.Sprintf("failed to read corpus : %v", err), http.StatusInternalServerError) return } w.Write(buf) } const ( DoHTML int = iota DoSubsystemCover DoModuleCover DoFuncCover DoFileCover DoRawCoverFiles DoRawCover DoFilterPCs DoCoverJSONL ) func (serv *HTTPServer) httpCover(w http.ResponseWriter, r *http.Request) { if !serv.Cfg.Cover { serv.httpCoverFallback(w, r) return } if r.FormValue("jsonl") == "1" { serv.httpCoverCover(w, r, DoCoverJSONL) return } serv.httpCoverCover(w, r, DoHTML) } func (serv *HTTPServer) httpSubsystemCover(w http.ResponseWriter, r *http.Request) { if !serv.Cfg.Cover { serv.httpCoverFallback(w, r) return } serv.httpCoverCover(w, r, DoSubsystemCover) } func (serv *HTTPServer) httpModuleCover(w http.ResponseWriter, r *http.Request) { if !serv.Cfg.Cover { serv.httpCoverFallback(w, r) return } serv.httpCoverCover(w, r, DoModuleCover) } const ctTextPlain = "text/plain; charset=utf-8" const ctApplicationJSON = "application/json" func (serv *HTTPServer) httpCoverCover(w http.ResponseWriter, r *http.Request, funcFlag int) { if !serv.Cfg.Cover { http.Error(w, "coverage is not enabled", http.StatusInternalServerError) return } coverInfo := serv.Cover.Load() if coverInfo == nil { http.Error(w, "coverage is not ready, please try again later after fuzzer started", http.StatusInternalServerError) return } rg, err := coverInfo.ReportGenerator.Get() if err != nil { http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) return } if r.FormValue("flush") != "" { defer func() { coverInfo.ReportGenerator.Reset() debug.FreeOSMemory() }() } var progs []cover.Prog if sig := r.FormValue("input"); sig != "" { inp := serv.Corpus.Item(sig) if inp == nil { http.Error(w, "unknown input hash", http.StatusInternalServerError) return } if r.FormValue("update_id") != "" { updateID, err := strconv.Atoi(r.FormValue("update_id")) if err != nil || updateID < 0 || updateID >= len(inp.Updates) { http.Error(w, "bad call_id", http.StatusBadRequest) return } progs = append(progs, cover.Prog{ Sig: sig, Data: string(inp.Prog.Serialize()), PCs: CoverToPCs(serv.Cfg, inp.Updates[updateID].RawCover), }) } else { progs = append(progs, cover.Prog{ Sig: sig, Data: string(inp.Prog.Serialize()), PCs: CoverToPCs(serv.Cfg, inp.Cover), }) } } else { call := r.FormValue("call") for _, inp := range serv.Corpus.Items() { if call != "" && call != inp.StringCall() { continue } progs = append(progs, cover.Prog{ Sig: inp.Sig, Data: string(inp.Prog.Serialize()), PCs: CoverToPCs(serv.Cfg, inp.Cover), }) } } var coverFilter map[uint64]struct{} if r.FormValue("filter") != "" || funcFlag == DoFilterPCs { if coverInfo.CoverFilter == nil { http.Error(w, "cover is not filtered in config", http.StatusInternalServerError) return } coverFilter = coverInfo.CoverFilter } params := cover.HandlerParams{ Progs: progs, Filter: coverFilter, Debug: r.FormValue("debug") != "", Force: r.FormValue("force") != "", } type handlerFuncType func(w io.Writer, params cover.HandlerParams) error flagToFunc := map[int]struct { Do handlerFuncType contentType string }{ DoHTML: {rg.DoHTML, ""}, DoSubsystemCover: {rg.DoSubsystemCover, ""}, DoModuleCover: {rg.DoModuleCover, ""}, DoFuncCover: {rg.DoFuncCover, ctTextPlain}, DoFileCover: {rg.DoFileCover, ctTextPlain}, DoRawCoverFiles: {rg.DoRawCoverFiles, ctTextPlain}, DoRawCover: {rg.DoRawCover, ctTextPlain}, DoFilterPCs: {rg.DoFilterPCs, ctTextPlain}, DoCoverJSONL: {rg.DoCoverJSONL, ctApplicationJSON}, } if ct := flagToFunc[funcFlag].contentType; ct != "" { w.Header().Set("Content-Type", ct) } if err := flagToFunc[funcFlag].Do(w, params); err != nil { http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) return } } func (serv *HTTPServer) httpCoverFallback(w http.ResponseWriter, r *http.Request) { calls := make(map[int][]int) for s := range serv.Corpus.Signal() { id, errno := prog.DecodeFallbackSignal(uint64(s)) calls[id] = append(calls[id], errno) } data := &UIFallbackCoverData{} if obj := serv.EnabledSyscalls.Load(); obj != nil { for call := range obj.(map[*prog.Syscall]bool) { errnos := calls[call.ID] sort.Ints(errnos) successful := 0 for len(errnos) != 0 && errnos[0] == 0 { successful++ errnos = errnos[1:] } data.Calls = append(data.Calls, UIFallbackCall{ Name: call.Name, Successful: successful, Errnos: errnos, }) } } sort.Slice(data.Calls, func(i, j int) bool { return data.Calls[i].Name < data.Calls[j].Name }) executeTemplate(w, fallbackCoverTemplate, data) } func (serv *HTTPServer) httpFuncCover(w http.ResponseWriter, r *http.Request) { serv.httpCoverCover(w, r, DoFuncCover) } func (serv *HTTPServer) httpFileCover(w http.ResponseWriter, r *http.Request) { serv.httpCoverCover(w, r, DoFileCover) } func (serv *HTTPServer) httpPrio(w http.ResponseWriter, r *http.Request) { callName := r.FormValue("call") call := serv.Cfg.Target.SyscallMap[callName] if call == nil { http.Error(w, fmt.Sprintf("unknown call: %v", callName), http.StatusInternalServerError) return } var corpus []*prog.Prog for _, inp := range serv.Corpus.Items() { corpus = append(corpus, inp.Prog) } prios := serv.Cfg.Target.CalculatePriorities(corpus) data := &UIPrioData{Call: callName} for i, p := range prios[call.ID] { data.Prios = append(data.Prios, UIPrio{serv.Cfg.Target.Syscalls[i].Name, p}) } sort.Slice(data.Prios, func(i, j int) bool { return data.Prios[i].Prio > data.Prios[j].Prio }) executeTemplate(w, prioTemplate, data) } func (serv *HTTPServer) httpFile(w http.ResponseWriter, r *http.Request) { file := filepath.Clean(r.FormValue("name")) if !strings.HasPrefix(file, "crashes/") && !strings.HasPrefix(file, "corpus/") { http.Error(w, "oh, oh, oh!", http.StatusInternalServerError) return } file = filepath.Join(serv.Cfg.Workdir, file) f, err := os.Open(file) if err != nil { http.Error(w, "failed to open the file", http.StatusInternalServerError) return } defer f.Close() w.Header().Set("Content-Type", "text/plain; charset=utf-8") io.Copy(w, f) } func (serv *HTTPServer) httpInput(w http.ResponseWriter, r *http.Request) { inp := serv.Corpus.Item(r.FormValue("sig")) if inp == nil { http.Error(w, "can't find the input", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Write(inp.Prog.Serialize()) } func (serv *HTTPServer) httpDebugInput(w http.ResponseWriter, r *http.Request) { inp := serv.Corpus.Item(r.FormValue("sig")) if inp == nil { http.Error(w, "can't find the input", http.StatusInternalServerError) return } getIDs := func(callID int) []int { ret := []int{} for id, update := range inp.Updates { if update.Call == callID { ret = append(ret, id) } } return ret } data := []UIRawCallCover{} for pos, line := range strings.Split(string(inp.Prog.Serialize()), "\n") { line = strings.TrimSpace(line) if line == "" { continue } data = append(data, UIRawCallCover{ Sig: r.FormValue("sig"), Call: line, UpdateIDs: getIDs(pos), }) } extraIDs := getIDs(-1) if len(extraIDs) > 0 { data = append(data, UIRawCallCover{ Sig: r.FormValue("sig"), Call: ".extra", UpdateIDs: extraIDs, }) } executeTemplate(w, rawCoverTemplate, data) } func (serv *HTTPServer) modulesInfo(w http.ResponseWriter, r *http.Request) { var modules []*vminfo.KernelModule if obj := serv.Cover.Load(); obj != nil { modules = obj.Modules } else { http.Error(w, "info is not ready, please try again later after fuzzer started", http.StatusInternalServerError) return } jsonModules, err := json.MarshalIndent(modules, "", "\t") if err != nil { fmt.Fprintf(w, "unable to create JSON modules info: %v", err) return } w.Header().Set("Content-Type", "application/json") w.Write(jsonModules) } var alphaNumRegExp = regexp.MustCompile(`^[a-zA-Z0-9]*$`) func isAlphanumeric(s string) bool { return alphaNumRegExp.MatchString(s) } func (serv *HTTPServer) httpReport(w http.ResponseWriter, r *http.Request) { crashID := r.FormValue("id") if !isAlphanumeric(crashID) { http.Error(w, "wrong id", http.StatusBadRequest) return } info, err := serv.CrashStore.Report(crashID) if err != nil { http.Error(w, fmt.Sprintf("%v", err), http.StatusBadRequest) return } commitDesc := "" if info.Tag != "" { commitDesc = fmt.Sprintf(" on commit %s.", info.Tag) } fmt.Fprintf(w, "Syzkaller hit '%s' bug%s.\n\n", info.Title, commitDesc) if len(info.Report) != 0 { fmt.Fprintf(w, "%s\n\n", info.Report) } if len(info.Prog) == 0 && len(info.CProg) == 0 { fmt.Fprintf(w, "The bug is not reproducible.\n") } else { fmt.Fprintf(w, "Syzkaller reproducer:\n%s\n\n", info.Prog) if len(info.CProg) != 0 { fmt.Fprintf(w, "C reproducer:\n%s\n\n", info.CProg) } } } func (serv *HTTPServer) httpRawCover(w http.ResponseWriter, r *http.Request) { serv.httpCoverCover(w, r, DoRawCover) } func (serv *HTTPServer) httpRawCoverFiles(w http.ResponseWriter, r *http.Request) { serv.httpCoverCover(w, r, DoRawCoverFiles) } func (serv *HTTPServer) httpFilterPCs(w http.ResponseWriter, r *http.Request) { serv.httpCoverCover(w, r, DoFilterPCs) } func (serv *HTTPServer) collectCrashes(workdir string) ([]*UICrashType, error) { var repros map[string]bool if reproLoop := serv.ReproLoop.Load(); reproLoop != nil { repros = reproLoop.Reproducing() } list, err := serv.CrashStore.BugList() if err != nil { return nil, err } var ret []*UICrashType for _, info := range list { ret = append(ret, makeUICrashType(info, serv.StartTime, repros)) } return ret, nil } func (serv *HTTPServer) httpJobs(w http.ResponseWriter, r *http.Request) { var list []*fuzzer.JobInfo if fuzzer := serv.Fuzzer.Load(); fuzzer != nil { list = fuzzer.RunningJobs() } if key := r.FormValue("id"); key != "" { for _, item := range list { if item.ID() == key { w.Write(item.Bytes()) return } } http.Error(w, "invalid job id (the job has likely already finished)", http.StatusBadRequest) return } jobType := r.FormValue("type") data := UIJobList{ Title: fmt.Sprintf("%s jobs", jobType), } switch jobType { case "triage": case "smash": case "hints": default: http.Error(w, "unknown job type", http.StatusBadRequest) return } for _, item := range list { if item.Type != jobType { continue } data.Jobs = append(data.Jobs, UIJobInfo{ ID: item.ID(), Short: item.Name, Execs: item.Execs.Load(), Calls: strings.Join(item.Calls, ", "), }) } sort.Slice(data.Jobs, func(i, j int) bool { a, b := data.Jobs[i], data.Jobs[j] return a.Short < b.Short }) executeTemplate(w, jobListTemplate, data) } func reproStatus(hasRepro, hasCRepro, reproducing, nonReproducible bool) string { status := "" if hasRepro { status = "has repro" if hasCRepro { status = "has C repro" } } else if reproducing { status = "reproducing" } else if nonReproducible { status = "non-reproducible" } return status } func executeTemplate(w http.ResponseWriter, templ *template.Template, data interface{}) { buf := new(bytes.Buffer) if err := templ.Execute(buf, data); err != nil { log.Logf(0, "failed to execute template: %v", err) http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) return } w.Write(buf.Bytes()) } type UISummaryData struct { Name string Revision string RevisionLink string Expert bool Stats []UIStat Crashes []*UICrashType Log string } type UIVMData struct { Name string VMs []UIVMInfo } type UIVMInfo struct { Name string State string Since time.Duration MachineInfo string DetailedStatus string } type UISyscallsData struct { Name string Calls []UICallType } type UICrashType struct { Description string LastTime time.Time Active bool ID string Count int Triaged string Strace string Crashes []*UICrash } type UICrash struct { *CrashInfo Active bool } type UIStat struct { Name string Value string Hint string Link string } type UICallType struct { Name string ID *int Inputs int Cover int } type UICorpus struct { Call string RawCover bool Inputs []*UIInput } type UIInput struct { Sig string Short string Cover int } var summaryTemplate = pages.Create(` {{.Name}} syzkaller {{HEAD}} {{.Name }} syzkaller [config] {{.Revision}} {{if .Expert}}disable{{else}}enable{{end}} expert mode
{{range $s := $.Stats}} {{end}}
Stats 📈
{{$s.Name}} {{if $s.Link}} {{$s.Value}} {{else}} {{$s.Value}} {{end}}
{{range $c := $.Crashes}} {{end}}
Crashes:
Description Count Last Time Report
{{$c.Description}} {{$c.Count}} {{formatTime $c.LastTime}} {{if $c.Triaged}} {{$c.Triaged}} {{end}} {{if $c.Strace}} Strace {{end}}
Log:
`) var vmsTemplate = pages.Create(` {{.Name }} syzkaller {{HEAD}} {{range $vm := $.VMs}} {{end}}
VM Info:
Name State Since Machine Info Status
{{$vm.Name}} {{$vm.State}} {{formatDuration $vm.Since}} {{optlink $vm.MachineInfo "info"}} {{optlink $vm.DetailedStatus "status"}}
`) var syscallsTemplate = pages.Create(` {{.Name }} syzkaller {{HEAD}} {{range $c := $.Calls}} {{end}}
Per-syscall coverage:
Syscall Inputs Coverage Prio
{{$c.Name}}{{if $c.ID }} [{{$c.ID}}]{{end}} {{$c.Inputs}} {{$c.Cover}} prio
`) var crashTemplate = pages.Create(` {{.Description}} {{HEAD}} {{.Description}} {{if .Triaged}} Report: {{.Triaged}} {{end}} {{range $c := $.Crashes}} {{end}} {{end}}
# Log Report Time Tag
{{$c.Index}} log {{if $c.Report}} report {{formatTime $c.Time}} {{formatTagHash $c.Tag}}
`) var corpusTemplate = pages.Create(` syzkaller corpus {{HEAD}} {{range $inp := $.Inputs}} {{end}}
Corpus{{if $.Call}} for {{$.Call}}{{end}}:
Coverage Program
{{$inp.Cover}} {{if $.RawCover}} / [raw] {{end}} {{$inp.Short}}
`) type UIPrioData struct { Call string Prios []UIPrio } type UIPrio struct { Call string Prio int32 } var prioTemplate = pages.Create(` syzkaller priorities {{HEAD}} {{range $p := $.Prios}} {{end}}
Priorities for {{$.Call}}:
Prio Call
{{printf "%5v" $p.Prio}} {{$p.Call}}
`) type UIFallbackCoverData struct { Calls []UIFallbackCall } type UIFallbackCall struct { Name string Successful int Errnos []int } var fallbackCoverTemplate = pages.Create(` syzkaller coverage {{HEAD}} {{range $c := $.Calls}} {{end}}
Call Successful Errnos
{{$c.Name}} {{if $c.Successful}}{{$c.Successful}}{{end}} {{range $e := $c.Errnos}}{{$e}} {{end}}
`) type UIRawCallCover struct { Sig string Call string UpdateIDs []int } var rawCoverTemplate = pages.Create(` syzkaller raw cover {{HEAD}} {{range $line := .}} {{end}}
Raw cover
Line Links
{{$line.Call}} {{range $id := $line.UpdateIDs}} [{{$id}}] {{end}}
`) type UIJobList struct { Title string Jobs []UIJobInfo } type UIJobInfo struct { ID string Short string Calls string Execs int32 } var jobListTemplate = pages.Create(` {{.Title}} {{HEAD}} {{range $job := $.Jobs}} {{end}}
{{.Title}} ({{len .Jobs}}):
Program Calls Execs
{{$job.Short}} {{$job.Calls}} {{$job.Execs}}
`)