github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/syz-manager/http.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 package main 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "fmt" 10 "html/template" 11 "io" 12 "net/http" 13 _ "net/http/pprof" 14 "os" 15 "path/filepath" 16 "regexp" 17 "runtime/debug" 18 "sort" 19 "strconv" 20 "strings" 21 "time" 22 23 "github.com/google/syzkaller/pkg/cover" 24 "github.com/google/syzkaller/pkg/html/pages" 25 "github.com/google/syzkaller/pkg/log" 26 "github.com/google/syzkaller/pkg/osutil" 27 "github.com/google/syzkaller/pkg/stats" 28 "github.com/google/syzkaller/pkg/vcs" 29 "github.com/google/syzkaller/prog" 30 "github.com/gorilla/handlers" 31 "github.com/prometheus/client_golang/prometheus" 32 "github.com/prometheus/client_golang/prometheus/promhttp" 33 ) 34 35 func (mgr *Manager) initHTTP() { 36 handle := func(pattern string, handler func(http.ResponseWriter, *http.Request)) { 37 http.Handle(pattern, handlers.CompressHandler(http.HandlerFunc(handler))) 38 } 39 handle("/", mgr.httpSummary) 40 handle("/config", mgr.httpConfig) 41 handle("/expert_mode", mgr.httpExpertMode) 42 handle("/stats", mgr.httpStats) 43 handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}).ServeHTTP) 44 handle("/syscalls", mgr.httpSyscalls) 45 handle("/corpus", mgr.httpCorpus) 46 handle("/corpus.db", mgr.httpDownloadCorpus) 47 handle("/crash", mgr.httpCrash) 48 handle("/cover", mgr.httpCover) 49 handle("/subsystemcover", mgr.httpSubsystemCover) 50 handle("/modulecover", mgr.httpModuleCover) 51 handle("/prio", mgr.httpPrio) 52 handle("/file", mgr.httpFile) 53 handle("/report", mgr.httpReport) 54 handle("/rawcover", mgr.httpRawCover) 55 handle("/rawcoverfiles", mgr.httpRawCoverFiles) 56 handle("/filterpcs", mgr.httpFilterPCs) 57 handle("/funccover", mgr.httpFuncCover) 58 handle("/filecover", mgr.httpFileCover) 59 handle("/input", mgr.httpInput) 60 handle("/debuginput", mgr.httpDebugInput) 61 handle("/modules", mgr.modulesInfo) 62 // Browsers like to request this, without special handler this goes to / handler. 63 handle("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {}) 64 65 log.Logf(0, "serving http on http://%v", mgr.cfg.HTTP) 66 go func() { 67 err := http.ListenAndServe(mgr.cfg.HTTP, nil) 68 if err != nil { 69 log.Fatalf("failed to listen on %v: %v", mgr.cfg.HTTP, err) 70 } 71 }() 72 } 73 74 func (mgr *Manager) httpSummary(w http.ResponseWriter, r *http.Request) { 75 data := &UISummaryData{ 76 Name: mgr.cfg.Name, 77 Revision: prog.GitRevisionBase[:8], 78 RevisionLink: vcs.LogLink(vcs.SyzkallerRepo, prog.GitRevisionBase), 79 Expert: mgr.expertMode, 80 Log: log.CachedLogOutput(), 81 } 82 83 level := stats.Simple 84 if mgr.expertMode { 85 level = stats.All 86 } 87 for _, stat := range stats.Collect(level) { 88 data.Stats = append(data.Stats, UIStat{ 89 Name: stat.Name, 90 Value: stat.Value, 91 Hint: stat.Desc, 92 Link: stat.Link, 93 }) 94 } 95 96 var err error 97 if data.Crashes, err = mgr.collectCrashes(mgr.cfg.Workdir); err != nil { 98 http.Error(w, fmt.Sprintf("failed to collect crashes: %v", err), http.StatusInternalServerError) 99 return 100 } 101 executeTemplate(w, summaryTemplate, data) 102 } 103 104 func (mgr *Manager) httpConfig(w http.ResponseWriter, r *http.Request) { 105 data, err := json.MarshalIndent(mgr.cfg, "", "\t") 106 if err != nil { 107 http.Error(w, fmt.Sprintf("failed to encode json: %v", err), 108 http.StatusInternalServerError) 109 return 110 } 111 w.Write(data) 112 } 113 114 func (mgr *Manager) httpExpertMode(w http.ResponseWriter, r *http.Request) { 115 mgr.expertMode = !mgr.expertMode 116 http.Redirect(w, r, "/", http.StatusFound) 117 } 118 119 func (mgr *Manager) httpSyscalls(w http.ResponseWriter, r *http.Request) { 120 data := &UISyscallsData{ 121 Name: mgr.cfg.Name, 122 } 123 for c, cc := range mgr.collectSyscallInfo() { 124 var syscallID *int 125 if syscall, ok := mgr.target.SyscallMap[c]; ok { 126 syscallID = &syscall.ID 127 } 128 data.Calls = append(data.Calls, UICallType{ 129 Name: c, 130 ID: syscallID, 131 Inputs: cc.Count, 132 Cover: len(cc.Cover), 133 }) 134 } 135 sort.Slice(data.Calls, func(i, j int) bool { 136 return data.Calls[i].Name < data.Calls[j].Name 137 }) 138 executeTemplate(w, syscallsTemplate, data) 139 } 140 141 func (mgr *Manager) httpStats(w http.ResponseWriter, r *http.Request) { 142 data, err := stats.RenderHTML() 143 if err != nil { 144 log.Logf(0, "failed to execute template: %v", err) 145 http.Error(w, err.Error(), http.StatusInternalServerError) 146 return 147 } 148 w.Write(data) 149 } 150 151 func (mgr *Manager) httpCrash(w http.ResponseWriter, r *http.Request) { 152 crashID := r.FormValue("id") 153 crash := readCrash(mgr.cfg.Workdir, crashID, nil, mgr.firstConnect.Load(), true) 154 if crash == nil { 155 http.Error(w, "failed to read crash info", http.StatusInternalServerError) 156 return 157 } 158 executeTemplate(w, crashTemplate, crash) 159 } 160 161 func (mgr *Manager) httpCorpus(w http.ResponseWriter, r *http.Request) { 162 mgr.mu.Lock() 163 defer mgr.mu.Unlock() 164 165 data := UICorpus{ 166 Call: r.FormValue("call"), 167 RawCover: mgr.cfg.RawCover, 168 } 169 for _, inp := range mgr.corpus.Items() { 170 if data.Call != "" && data.Call != inp.StringCall() { 171 continue 172 } 173 data.Inputs = append(data.Inputs, &UIInput{ 174 Sig: inp.Sig, 175 Short: inp.Prog.String(), 176 Cover: len(inp.Cover), 177 }) 178 } 179 sort.Slice(data.Inputs, func(i, j int) bool { 180 a, b := data.Inputs[i], data.Inputs[j] 181 if a.Cover != b.Cover { 182 return a.Cover > b.Cover 183 } 184 return a.Short < b.Short 185 }) 186 executeTemplate(w, corpusTemplate, data) 187 } 188 189 func (mgr *Manager) httpDownloadCorpus(w http.ResponseWriter, r *http.Request) { 190 corpus := filepath.Join(mgr.cfg.Workdir, "corpus.db") 191 file, err := os.Open(corpus) 192 if err != nil { 193 http.Error(w, fmt.Sprintf("failed to open corpus : %v", err), http.StatusInternalServerError) 194 return 195 } 196 defer file.Close() 197 buf, err := io.ReadAll(file) 198 if err != nil { 199 http.Error(w, fmt.Sprintf("failed to read corpus : %v", err), http.StatusInternalServerError) 200 return 201 } 202 w.Write(buf) 203 } 204 205 const ( 206 DoHTML int = iota 207 DoHTMLTable 208 DoModuleCover 209 DoCSV 210 DoCSVFiles 211 DoRawCoverFiles 212 DoRawCover 213 DoFilterPCs 214 DoCoverJSONL 215 ) 216 217 func (mgr *Manager) httpCover(w http.ResponseWriter, r *http.Request) { 218 if !mgr.cfg.Cover { 219 mgr.httpCoverFallback(w, r) 220 return 221 } 222 if r.FormValue("jsonl") == "1" { 223 mgr.httpCoverCover(w, r, DoCoverJSONL) 224 return 225 } 226 mgr.httpCoverCover(w, r, DoHTML) 227 } 228 229 func (mgr *Manager) httpSubsystemCover(w http.ResponseWriter, r *http.Request) { 230 if !mgr.cfg.Cover { 231 mgr.httpCoverFallback(w, r) 232 return 233 } 234 mgr.httpCoverCover(w, r, DoHTMLTable) 235 } 236 237 func (mgr *Manager) httpModuleCover(w http.ResponseWriter, r *http.Request) { 238 if !mgr.cfg.Cover { 239 mgr.httpCoverFallback(w, r) 240 return 241 } 242 mgr.httpCoverCover(w, r, DoModuleCover) 243 } 244 245 const ctTextPlain = "text/plain; charset=utf-8" 246 const ctApplicationJSON = "application/json" 247 248 func (mgr *Manager) httpCoverCover(w http.ResponseWriter, r *http.Request, funcFlag int) { 249 if !mgr.cfg.Cover { 250 http.Error(w, "coverage is not enabled", http.StatusInternalServerError) 251 return 252 } 253 254 // Don't hold the mutex while creating report generator and generating the report, 255 // these operations take lots of time. 256 if !mgr.serv.checkDone.Load() { 257 http.Error(w, "coverage is not ready, please try again later after fuzzer started", http.StatusInternalServerError) 258 return 259 } 260 261 rg, err := getReportGenerator(mgr.cfg, mgr.serv.modules) 262 if err != nil { 263 http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) 264 return 265 } 266 267 if r.FormValue("flush") != "" { 268 defer func() { 269 resetReportGenerator() 270 debug.FreeOSMemory() 271 }() 272 } 273 274 mgr.mu.Lock() 275 var progs []cover.Prog 276 if sig := r.FormValue("input"); sig != "" { 277 inp := mgr.corpus.Item(sig) 278 if inp == nil { 279 http.Error(w, "unknown input hash", http.StatusInternalServerError) 280 return 281 } 282 if r.FormValue("update_id") != "" { 283 updateID, err := strconv.Atoi(r.FormValue("update_id")) 284 if err != nil || updateID < 0 || updateID >= len(inp.Updates) { 285 http.Error(w, "bad call_id", http.StatusBadRequest) 286 return 287 } 288 progs = append(progs, cover.Prog{ 289 Sig: sig, 290 Data: string(inp.ProgData), 291 PCs: coverToPCs(rg, inp.Updates[updateID].RawCover), 292 }) 293 } else { 294 progs = append(progs, cover.Prog{ 295 Sig: sig, 296 Data: string(inp.ProgData), 297 PCs: coverToPCs(rg, inp.Cover), 298 }) 299 } 300 } else { 301 call := r.FormValue("call") 302 for _, inp := range mgr.corpus.Items() { 303 if call != "" && call != inp.StringCall() { 304 continue 305 } 306 progs = append(progs, cover.Prog{ 307 Sig: inp.Sig, 308 Data: string(inp.ProgData), 309 PCs: coverToPCs(rg, inp.Cover), 310 }) 311 } 312 } 313 mgr.mu.Unlock() 314 315 var coverFilter map[uint32]uint32 316 if r.FormValue("filter") != "" || funcFlag == DoFilterPCs { 317 if mgr.serv.coverFilter == nil { 318 http.Error(w, "cover is not filtered in config", http.StatusInternalServerError) 319 return 320 } 321 coverFilter = mgr.serv.coverFilter 322 } 323 324 params := cover.CoverHandlerParams{ 325 Progs: progs, 326 CoverFilter: coverFilter, 327 Debug: r.FormValue("debug") != "", 328 Force: r.FormValue("force") != "", 329 } 330 331 type handlerFuncType func(w io.Writer, params cover.CoverHandlerParams) error 332 flagToFunc := map[int]struct { 333 Do handlerFuncType 334 contentType string 335 }{ 336 DoHTML: {rg.DoHTML, ""}, 337 DoHTMLTable: {rg.DoHTMLTable, ""}, 338 DoModuleCover: {rg.DoModuleCover, ""}, 339 DoCSV: {rg.DoCSV, ctTextPlain}, 340 DoCSVFiles: {rg.DoCSVFiles, ctTextPlain}, 341 DoRawCoverFiles: {rg.DoRawCoverFiles, ctTextPlain}, 342 DoRawCover: {rg.DoRawCover, ctTextPlain}, 343 DoFilterPCs: {rg.DoFilterPCs, ctTextPlain}, 344 DoCoverJSONL: {rg.DoCoverJSONL, ctApplicationJSON}, 345 } 346 347 if ct := flagToFunc[funcFlag].contentType; ct != "" { 348 w.Header().Set("Content-Type", ct) 349 } 350 351 if err := flagToFunc[funcFlag].Do(w, params); err != nil { 352 http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) 353 return 354 } 355 } 356 357 func (mgr *Manager) httpCoverFallback(w http.ResponseWriter, r *http.Request) { 358 mgr.mu.Lock() 359 defer mgr.mu.Unlock() 360 calls := make(map[int][]int) 361 for s := range mgr.corpus.Signal() { 362 id, errno := prog.DecodeFallbackSignal(uint32(s)) 363 calls[id] = append(calls[id], errno) 364 } 365 data := &UIFallbackCoverData{} 366 for call := range mgr.targetEnabledSyscalls { 367 errnos := calls[call.ID] 368 sort.Ints(errnos) 369 successful := 0 370 for len(errnos) != 0 && errnos[0] == 0 { 371 successful++ 372 errnos = errnos[1:] 373 } 374 data.Calls = append(data.Calls, UIFallbackCall{ 375 Name: call.Name, 376 Successful: successful, 377 Errnos: errnos, 378 }) 379 } 380 sort.Slice(data.Calls, func(i, j int) bool { 381 return data.Calls[i].Name < data.Calls[j].Name 382 }) 383 executeTemplate(w, fallbackCoverTemplate, data) 384 } 385 386 func (mgr *Manager) httpFuncCover(w http.ResponseWriter, r *http.Request) { 387 mgr.httpCoverCover(w, r, DoCSV) 388 } 389 390 func (mgr *Manager) httpFileCover(w http.ResponseWriter, r *http.Request) { 391 mgr.httpCoverCover(w, r, DoCSVFiles) 392 } 393 394 func (mgr *Manager) httpPrio(w http.ResponseWriter, r *http.Request) { 395 mgr.mu.Lock() 396 defer mgr.mu.Unlock() 397 398 callName := r.FormValue("call") 399 call := mgr.target.SyscallMap[callName] 400 if call == nil { 401 http.Error(w, fmt.Sprintf("unknown call: %v", callName), http.StatusInternalServerError) 402 return 403 } 404 405 var corpus []*prog.Prog 406 for _, inp := range mgr.corpus.Items() { 407 corpus = append(corpus, inp.Prog) 408 } 409 prios := mgr.target.CalculatePriorities(corpus) 410 411 data := &UIPrioData{Call: callName} 412 for i, p := range prios[call.ID] { 413 data.Prios = append(data.Prios, UIPrio{mgr.target.Syscalls[i].Name, p}) 414 } 415 sort.Slice(data.Prios, func(i, j int) bool { 416 return data.Prios[i].Prio > data.Prios[j].Prio 417 }) 418 executeTemplate(w, prioTemplate, data) 419 } 420 421 func (mgr *Manager) httpFile(w http.ResponseWriter, r *http.Request) { 422 file := filepath.Clean(r.FormValue("name")) 423 if !strings.HasPrefix(file, "crashes/") && !strings.HasPrefix(file, "corpus/") { 424 http.Error(w, "oh, oh, oh!", http.StatusInternalServerError) 425 return 426 } 427 file = filepath.Join(mgr.cfg.Workdir, file) 428 f, err := os.Open(file) 429 if err != nil { 430 http.Error(w, "failed to open the file", http.StatusInternalServerError) 431 return 432 } 433 defer f.Close() 434 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 435 io.Copy(w, f) 436 } 437 438 func (mgr *Manager) httpInput(w http.ResponseWriter, r *http.Request) { 439 mgr.mu.Lock() 440 defer mgr.mu.Unlock() 441 inp := mgr.corpus.Item(r.FormValue("sig")) 442 if inp == nil { 443 http.Error(w, "can't find the input", http.StatusInternalServerError) 444 return 445 } 446 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 447 w.Write(inp.ProgData) 448 } 449 450 func (mgr *Manager) httpDebugInput(w http.ResponseWriter, r *http.Request) { 451 mgr.mu.Lock() 452 defer mgr.mu.Unlock() 453 inp := mgr.corpus.Item(r.FormValue("sig")) 454 if inp == nil { 455 http.Error(w, "can't find the input", http.StatusInternalServerError) 456 return 457 } 458 getIDs := func(callID int) []int { 459 ret := []int{} 460 for id, update := range inp.Updates { 461 if update.Call == callID { 462 ret = append(ret, id) 463 } 464 } 465 return ret 466 } 467 data := []UIRawCallCover{} 468 for pos, line := range strings.Split(string(inp.ProgData), "\n") { 469 line = strings.TrimSpace(line) 470 if line == "" { 471 continue 472 } 473 data = append(data, UIRawCallCover{ 474 Sig: r.FormValue("sig"), 475 Call: line, 476 UpdateIDs: getIDs(pos), 477 }) 478 } 479 extraIDs := getIDs(-1) 480 if len(extraIDs) > 0 { 481 data = append(data, UIRawCallCover{ 482 Sig: r.FormValue("sig"), 483 Call: ".extra", 484 UpdateIDs: extraIDs, 485 }) 486 } 487 executeTemplate(w, rawCoverTemplate, data) 488 } 489 490 func (mgr *Manager) modulesInfo(w http.ResponseWriter, r *http.Request) { 491 if mgr.serv.canonicalModules == nil { 492 fmt.Fprintf(w, "module information not retrieved yet, please retry after fuzzing starts\n") 493 return 494 } 495 // NewCanonicalizer() is initialized with serv.modules. 496 modules, err := json.MarshalIndent(mgr.serv.modules, "", "\t") 497 if err != nil { 498 fmt.Fprintf(w, "unable to create JSON modules info: %v", err) 499 return 500 } 501 w.Header().Set("Content-Type", "application/json") 502 w.Write(modules) 503 } 504 505 var alphaNumRegExp = regexp.MustCompile(`^[a-zA-Z0-9]*$`) 506 507 func isAlphanumeric(s string) bool { 508 return alphaNumRegExp.MatchString(s) 509 } 510 511 func (mgr *Manager) httpReport(w http.ResponseWriter, r *http.Request) { 512 mgr.mu.Lock() 513 defer mgr.mu.Unlock() 514 515 crashID := r.FormValue("id") 516 if !isAlphanumeric(crashID) { 517 http.Error(w, "wrong id", http.StatusBadRequest) 518 return 519 } 520 521 desc, err := os.ReadFile(filepath.Join(mgr.crashdir, crashID, "description")) 522 if err != nil { 523 http.Error(w, "failed to read description file", http.StatusInternalServerError) 524 return 525 } 526 tag, _ := os.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.tag")) 527 prog, _ := os.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.prog")) 528 cprog, _ := os.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.cprog")) 529 rep, _ := os.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.report")) 530 531 commitDesc := "" 532 if len(tag) != 0 { 533 commitDesc = fmt.Sprintf(" on commit %s.", trimNewLines(tag)) 534 } 535 fmt.Fprintf(w, "Syzkaller hit '%s' bug%s.\n\n", trimNewLines(desc), commitDesc) 536 if len(rep) != 0 { 537 fmt.Fprintf(w, "%s\n\n", rep) 538 } 539 if len(prog) == 0 && len(cprog) == 0 { 540 fmt.Fprintf(w, "The bug is not reproducible.\n") 541 } else { 542 fmt.Fprintf(w, "Syzkaller reproducer:\n%s\n\n", prog) 543 if len(cprog) != 0 { 544 fmt.Fprintf(w, "C reproducer:\n%s\n\n", cprog) 545 } 546 } 547 } 548 549 func (mgr *Manager) httpRawCover(w http.ResponseWriter, r *http.Request) { 550 mgr.httpCoverCover(w, r, DoRawCover) 551 } 552 553 func (mgr *Manager) httpRawCoverFiles(w http.ResponseWriter, r *http.Request) { 554 mgr.httpCoverCover(w, r, DoRawCoverFiles) 555 } 556 557 func (mgr *Manager) httpFilterPCs(w http.ResponseWriter, r *http.Request) { 558 mgr.httpCoverCover(w, r, DoFilterPCs) 559 } 560 561 func (mgr *Manager) collectCrashes(workdir string) ([]*UICrashType, error) { 562 // Note: mu is not locked here. 563 reproReply := make(chan map[string]bool) 564 mgr.reproRequest <- reproReply 565 repros := <-reproReply 566 567 crashdir := filepath.Join(workdir, "crashes") 568 dirs, err := osutil.ListDir(crashdir) 569 if err != nil { 570 return nil, err 571 } 572 var crashTypes []*UICrashType 573 for _, dir := range dirs { 574 crash := readCrash(workdir, dir, repros, mgr.firstConnect.Load(), false) 575 if crash != nil { 576 crashTypes = append(crashTypes, crash) 577 } 578 } 579 sort.Slice(crashTypes, func(i, j int) bool { 580 return strings.ToLower(crashTypes[i].Description) < strings.ToLower(crashTypes[j].Description) 581 }) 582 return crashTypes, nil 583 } 584 585 func readCrash(workdir, dir string, repros map[string]bool, start int64, full bool) *UICrashType { 586 if len(dir) != 40 { 587 return nil 588 } 589 crashdir := filepath.Join(workdir, "crashes") 590 descFile, err := os.Open(filepath.Join(crashdir, dir, "description")) 591 if err != nil { 592 return nil 593 } 594 defer descFile.Close() 595 descBytes, err := io.ReadAll(descFile) 596 if err != nil || len(descBytes) == 0 { 597 return nil 598 } 599 desc := string(trimNewLines(descBytes)) 600 stat, err := descFile.Stat() 601 if err != nil { 602 return nil 603 } 604 modTime := stat.ModTime() 605 descFile.Close() 606 607 files, err := osutil.ListDir(filepath.Join(crashdir, dir)) 608 if err != nil { 609 return nil 610 } 611 var crashes []*UICrash 612 reproAttempts := 0 613 hasRepro, hasCRepro := false, false 614 strace := "" 615 reports := make(map[string]bool) 616 for _, f := range files { 617 if strings.HasPrefix(f, "log") { 618 index, err := strconv.ParseUint(f[3:], 10, 64) 619 if err == nil { 620 crashes = append(crashes, &UICrash{ 621 Index: int(index), 622 }) 623 } 624 } else if strings.HasPrefix(f, "report") { 625 reports[f] = true 626 } else if f == "repro.prog" { 627 hasRepro = true 628 } else if f == "repro.cprog" { 629 hasCRepro = true 630 } else if f == "repro.report" { 631 } else if f == "repro0" || f == "repro1" || f == "repro2" { 632 reproAttempts++ 633 } else if f == "strace.log" { 634 strace = filepath.Join("crashes", dir, f) 635 } 636 } 637 638 if full { 639 for _, crash := range crashes { 640 index := strconv.Itoa(crash.Index) 641 crash.Log = filepath.Join("crashes", dir, "log"+index) 642 if stat, err := os.Stat(filepath.Join(workdir, crash.Log)); err == nil { 643 crash.Time = stat.ModTime() 644 crash.Active = start != 0 && crash.Time.Unix() >= start 645 } 646 tag, _ := os.ReadFile(filepath.Join(crashdir, dir, "tag"+index)) 647 crash.Tag = string(tag) 648 reportFile := filepath.Join("crashes", dir, "report"+index) 649 if osutil.IsExist(filepath.Join(workdir, reportFile)) { 650 crash.Report = reportFile 651 } 652 } 653 sort.Slice(crashes, func(i, j int) bool { 654 return crashes[i].Time.After(crashes[j].Time) 655 }) 656 } 657 658 triaged := reproStatus(hasRepro, hasCRepro, repros[desc], reproAttempts >= maxReproAttempts) 659 return &UICrashType{ 660 Description: desc, 661 LastTime: modTime, 662 Active: start != 0 && modTime.Unix() >= start, 663 ID: dir, 664 Count: len(crashes), 665 Triaged: triaged, 666 Strace: strace, 667 Crashes: crashes, 668 } 669 } 670 671 func reproStatus(hasRepro, hasCRepro, reproducing, nonReproducible bool) string { 672 status := "" 673 if hasRepro { 674 status = "has repro" 675 if hasCRepro { 676 status = "has C repro" 677 } 678 } else if reproducing { 679 status = "reproducing" 680 } else if nonReproducible { 681 status = "non-reproducible" 682 } 683 return status 684 } 685 686 func executeTemplate(w http.ResponseWriter, templ *template.Template, data interface{}) { 687 buf := new(bytes.Buffer) 688 if err := templ.Execute(buf, data); err != nil { 689 log.Logf(0, "failed to execute template: %v", err) 690 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) 691 return 692 } 693 w.Write(buf.Bytes()) 694 } 695 696 func trimNewLines(data []byte) []byte { 697 for len(data) > 0 && data[len(data)-1] == '\n' { 698 data = data[:len(data)-1] 699 } 700 return data 701 } 702 703 type UISummaryData struct { 704 Name string 705 Revision string 706 RevisionLink string 707 Expert bool 708 Stats []UIStat 709 Crashes []*UICrashType 710 Log string 711 } 712 713 type UISyscallsData struct { 714 Name string 715 Calls []UICallType 716 } 717 718 type UICrashType struct { 719 Description string 720 LastTime time.Time 721 Active bool 722 ID string 723 Count int 724 Triaged string 725 Strace string 726 Crashes []*UICrash 727 } 728 729 type UICrash struct { 730 Index int 731 Time time.Time 732 Active bool 733 Log string 734 Report string 735 Tag string 736 } 737 738 type UIStat struct { 739 Name string 740 Value string 741 Hint string 742 Link string 743 } 744 745 type UICallType struct { 746 Name string 747 ID *int 748 Inputs int 749 Cover int 750 } 751 752 type UICorpus struct { 753 Call string 754 RawCover bool 755 Inputs []*UIInput 756 } 757 758 type UIInput struct { 759 Sig string 760 Short string 761 Cover int 762 } 763 764 var summaryTemplate = pages.Create(` 765 <!doctype html> 766 <html> 767 <head> 768 <title>{{.Name}} syzkaller</title> 769 {{HEAD}} 770 </head> 771 <body> 772 <b>{{.Name }} syzkaller</b> 773 <a href='/config'>[config]</a> 774 <a href='{{.RevisionLink}}'>{{.Revision}}</a> 775 <a class="navigation_tab" href='expert_mode'>{{if .Expert}}disable{{else}}enable{{end}} expert mode</a> 776 <br> 777 778 <table class="list_table"> 779 <caption><a href='/stats'>Stats 📈</a></caption> 780 {{range $s := $.Stats}} 781 <tr> 782 <td class="stat_name" title="{{$s.Hint}}">{{$s.Name}}</td> 783 <td class="stat_value"> 784 {{if $s.Link}} 785 <a href="{{$s.Link}}">{{$s.Value}}</a> 786 {{else}} 787 {{$s.Value}} 788 {{end}} 789 </td> 790 </tr> 791 {{end}} 792 </table> 793 794 <table class="list_table"> 795 <caption>Crashes:</caption> 796 <tr> 797 <th><a onclick="return sortTable(this, 'Description', textSort)" href="#">Description</a></th> 798 <th><a onclick="return sortTable(this, 'Count', numSort)" href="#">Count</a></th> 799 <th><a onclick="return sortTable(this, 'Last Time', textSort, true)" href="#">Last Time</a></th> 800 <th><a onclick="return sortTable(this, 'Report', textSort)" href="#">Report</a></th> 801 </tr> 802 {{range $c := $.Crashes}} 803 <tr> 804 <td class="title"><a href="/crash?id={{$c.ID}}">{{$c.Description}}</a></td> 805 <td class="stat {{if not $c.Active}}inactive{{end}}">{{$c.Count}}</td> 806 <td class="time {{if not $c.Active}}inactive{{end}}">{{formatTime $c.LastTime}}</td> 807 <td> 808 {{if $c.Triaged}} 809 <a href="/report?id={{$c.ID}}">{{$c.Triaged}}</a> 810 {{end}} 811 {{if $c.Strace}} 812 <a href="/file?name={{$c.Strace}}">Strace</a> 813 {{end}} 814 </td> 815 </tr> 816 {{end}} 817 </table> 818 819 <b>Log:</b> 820 <br> 821 <textarea id="log_textarea" readonly rows="20" wrap=off> 822 {{.Log}} 823 </textarea> 824 <script> 825 var textarea = document.getElementById("log_textarea"); 826 textarea.scrollTop = textarea.scrollHeight; 827 </script> 828 </body></html> 829 `) 830 831 var syscallsTemplate = pages.Create(` 832 <!doctype html> 833 <html> 834 <head> 835 <title>{{.Name }} syzkaller</title> 836 {{HEAD}} 837 </head> 838 <body> 839 840 <table class="list_table"> 841 <caption>Per-syscall coverage:</caption> 842 <tr> 843 <th><a onclick="return sortTable(this, 'Syscall', textSort)" href="#">Syscall</a></th> 844 <th><a onclick="return sortTable(this, 'Inputs', numSort)" href="#">Inputs</a></th> 845 <th><a onclick="return sortTable(this, 'Coverage', numSort)" href="#">Coverage</a></th> 846 <th>Prio</th> 847 </tr> 848 {{range $c := $.Calls}} 849 <tr> 850 <td>{{$c.Name}}{{if $c.ID }} [{{$c.ID}}]{{end}}</td> 851 <td><a href='/corpus?call={{$c.Name}}'>{{$c.Inputs}}</a></td> 852 <td><a href='/cover?call={{$c.Name}}'>{{$c.Cover}}</a></td> 853 <td><a href='/prio?call={{$c.Name}}'>prio</a></td> 854 </tr> 855 {{end}} 856 </table> 857 </body></html> 858 `) 859 860 var crashTemplate = pages.Create(` 861 <!doctype html> 862 <html> 863 <head> 864 <title>{{.Description}}</title> 865 {{HEAD}} 866 </head> 867 <body> 868 <b>{{.Description}}</b> 869 870 {{if .Triaged}} 871 Report: <a href="/report?id={{.ID}}">{{.Triaged}}</a> 872 {{end}} 873 874 <table class="list_table"> 875 <tr> 876 <th>#</th> 877 <th>Log</th> 878 <th>Report</th> 879 <th>Time</th> 880 <th>Tag</th> 881 </tr> 882 {{range $c := $.Crashes}} 883 <tr> 884 <td>{{$c.Index}}</td> 885 <td><a href="/file?name={{$c.Log}}">log</a></td> 886 <td> 887 {{if $c.Report}} 888 <a href="/file?name={{$c.Report}}">report</a></td> 889 {{end}} 890 </td> 891 <td class="time {{if not $c.Active}}inactive{{end}}">{{formatTime $c.Time}}</td> 892 <td class="tag {{if not $c.Active}}inactive{{end}}" title="{{$c.Tag}}">{{formatTagHash $c.Tag}}</td> 893 </tr> 894 {{end}} 895 </table> 896 </body></html> 897 `) 898 899 var corpusTemplate = pages.Create(` 900 <!doctype html> 901 <html> 902 <head> 903 <title>syzkaller corpus</title> 904 {{HEAD}} 905 </head> 906 <body> 907 908 <table class="list_table"> 909 <caption>Corpus{{if $.Call}} for {{$.Call}}{{end}}:</caption> 910 <tr> 911 <th>Coverage</th> 912 <th>Program</th> 913 </tr> 914 {{range $inp := $.Inputs}} 915 <tr> 916 <td> 917 <a href='/cover?input={{$inp.Sig}}'>{{$inp.Cover}}</a> 918 {{if $.RawCover}} 919 / <a href="/debuginput?sig={{$inp.Sig}}">[raw]</a> 920 {{end}} 921 </td> 922 <td><a href="/input?sig={{$inp.Sig}}">{{$inp.Short}}</a></td> 923 </tr> 924 {{end}} 925 </table> 926 </body></html> 927 `) 928 929 type UIPrioData struct { 930 Call string 931 Prios []UIPrio 932 } 933 934 type UIPrio struct { 935 Call string 936 Prio int32 937 } 938 939 var prioTemplate = pages.Create(` 940 <!doctype html> 941 <html> 942 <head> 943 <title>syzkaller priorities</title> 944 {{HEAD}} 945 </head> 946 <body> 947 <table class="list_table"> 948 <caption>Priorities for {{$.Call}}:</caption> 949 <tr> 950 <th><a onclick="return sortTable(this, 'Prio', floatSort)" href="#">Prio</a></th> 951 <th><a onclick="return sortTable(this, 'Call', textSort)" href="#">Call</a></th> 952 </tr> 953 {{range $p := $.Prios}} 954 <tr> 955 <td>{{printf "%5v" $p.Prio}}</td> 956 <td><a href='/prio?call={{$p.Call}}'>{{$p.Call}}</a></td> 957 </tr> 958 {{end}} 959 </table> 960 </body></html> 961 `) 962 963 type UIFallbackCoverData struct { 964 Calls []UIFallbackCall 965 } 966 967 type UIFallbackCall struct { 968 Name string 969 Successful int 970 Errnos []int 971 } 972 973 var fallbackCoverTemplate = pages.Create(` 974 <!doctype html> 975 <html> 976 <head> 977 <title>syzkaller coverage</title> 978 {{HEAD}} 979 </head> 980 <body> 981 <table class="list_table"> 982 <tr> 983 <th>Call</th> 984 <th>Successful</th> 985 <th>Errnos</th> 986 </tr> 987 {{range $c := $.Calls}} 988 <tr> 989 <td>{{$c.Name}}</td> 990 <td>{{if $c.Successful}}{{$c.Successful}}{{end}}</td> 991 <td>{{range $e := $c.Errnos}}{{$e}} {{end}}</td> 992 </tr> 993 {{end}} 994 </table> 995 </body></html> 996 `) 997 998 type UIRawCallCover struct { 999 Sig string 1000 Call string 1001 UpdateIDs []int 1002 } 1003 1004 var rawCoverTemplate = pages.Create(` 1005 <!doctype html> 1006 <html> 1007 <head> 1008 <title>syzkaller raw cover</title> 1009 {{HEAD}} 1010 </head> 1011 <body> 1012 1013 <table class="list_table"> 1014 <caption>Raw cover</caption> 1015 <tr> 1016 <th>Line</th> 1017 <th>Links</th> 1018 </tr> 1019 {{range $line := .}} 1020 <tr> 1021 <td>{{$line.Call}}</td> 1022 <td> 1023 {{range $id := $line.UpdateIDs}} 1024 <a href="/rawcover?input={{$line.Sig}}&update_id={{$id}}">[{{$id}}]</a> 1025 {{end}} 1026 </td> 1027 </tr> 1028 {{end}} 1029 </table> 1030 </body></html> 1031 `)