github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/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 manager 5 6 import ( 7 "bytes" 8 "context" 9 "embed" 10 "encoding/json" 11 "fmt" 12 "html/template" 13 "io" 14 "net/http" 15 _ "net/http/pprof" 16 "os" 17 "path/filepath" 18 "regexp" 19 "runtime/debug" 20 "sort" 21 "strconv" 22 "strings" 23 "sync/atomic" 24 "time" 25 26 "github.com/google/syzkaller/pkg/corpus" 27 "github.com/google/syzkaller/pkg/cover" 28 "github.com/google/syzkaller/pkg/fuzzer" 29 "github.com/google/syzkaller/pkg/html/pages" 30 "github.com/google/syzkaller/pkg/log" 31 "github.com/google/syzkaller/pkg/mgrconfig" 32 "github.com/google/syzkaller/pkg/report" 33 "github.com/google/syzkaller/pkg/stat" 34 "github.com/google/syzkaller/pkg/vcs" 35 "github.com/google/syzkaller/pkg/vminfo" 36 "github.com/google/syzkaller/prog" 37 "github.com/google/syzkaller/vm" 38 "github.com/google/syzkaller/vm/dispatcher" 39 "github.com/gorilla/handlers" 40 "github.com/prometheus/client_golang/prometheus" 41 "github.com/prometheus/client_golang/prometheus/promhttp" 42 ) 43 44 type CoverageInfo struct { 45 Modules []*vminfo.KernelModule 46 ReportGenerator *ReportGeneratorWrapper 47 CoverFilter map[uint64]struct{} 48 } 49 50 type HTTPServer struct { 51 // To be set before calling Serve. 52 Cfg *mgrconfig.Config 53 StartTime time.Time 54 CrashStore *CrashStore 55 DiffStore *DiffFuzzerStore 56 ReproLoop *ReproLoop 57 Pool *vm.Dispatcher 58 Pools map[string]*vm.Dispatcher 59 TogglePause func(paused bool) 60 61 // Can be set dynamically after calling Serve. 62 Corpus atomic.Pointer[corpus.Corpus] 63 Fuzzer atomic.Pointer[fuzzer.Fuzzer] 64 Cover atomic.Pointer[CoverageInfo] 65 EnabledSyscalls atomic.Value // map[*prog.Syscall]bool 66 67 // Internal state. 68 expertMode bool 69 paused bool 70 } 71 72 func (serv *HTTPServer) Serve(ctx context.Context) error { 73 if serv.Cfg.HTTP == "" { 74 return fmt.Errorf("starting a disabled HTTP server") 75 } 76 if serv.Pool != nil { 77 serv.Pools = map[string]*vm.Dispatcher{"": serv.Pool} 78 } 79 handle := func(pattern string, handler func(http.ResponseWriter, *http.Request)) { 80 http.Handle(pattern, handlers.CompressHandler(http.HandlerFunc(handler))) 81 } 82 // keep-sorted start 83 handle("/", serv.httpMain) 84 handle("/action", serv.httpAction) 85 handle("/addcandidate", serv.httpAddCandidate) 86 handle("/config", serv.httpConfig) 87 handle("/corpus", serv.httpCorpus) 88 handle("/corpus.db", serv.httpDownloadCorpus) 89 handle("/cover", serv.httpCover) 90 handle("/coverprogs", serv.httpPrograms) 91 handle("/debuginput", serv.httpDebugInput) 92 handle("/file", serv.httpFile) 93 handle("/filecover", serv.httpFileCover) 94 handle("/filterpcs", serv.httpFilterPCs) 95 handle("/funccover", serv.httpFuncCover) 96 handle("/input", serv.httpInput) 97 handle("/jobs", serv.httpJobs) 98 handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}).ServeHTTP) 99 handle("/modulecover", serv.httpModuleCover) 100 handle("/modules", serv.modulesInfo) 101 handle("/prio", serv.httpPrio) 102 handle("/rawcover", serv.httpRawCover) 103 handle("/rawcoverfiles", serv.httpRawCoverFiles) 104 handle("/stats", serv.httpStats) 105 handle("/subsystemcover", serv.httpSubsystemCover) 106 handle("/syscalls", serv.httpSyscalls) 107 handle("/vm", serv.httpVM) 108 handle("/vms", serv.httpVMs) 109 // keep-sorted end 110 if serv.CrashStore != nil { 111 handle("/crash", serv.httpCrash) 112 handle("/report", serv.httpReport) 113 } 114 // Browsers like to request this, without special handler this goes to / handler. 115 handle("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {}) 116 117 log.Logf(0, "serving http on http://%v", serv.Cfg.HTTP) 118 server := &http.Server{Addr: serv.Cfg.HTTP} 119 go func() { 120 // The http server package unfortunately does not natively take a context.Context. 121 // Let's emulate it via server.Shutdown() 122 <-ctx.Done() 123 server.Close() 124 }() 125 126 err := server.ListenAndServe() 127 if err != http.ErrServerClosed { 128 return err 129 } 130 return nil 131 } 132 133 func (serv *HTTPServer) httpAction(w http.ResponseWriter, r *http.Request) { 134 switch r.FormValue("toggle") { 135 case "expert": 136 serv.expertMode = !serv.expertMode 137 case "pause": 138 if serv.TogglePause == nil { 139 http.Error(w, "pause is not implemented", http.StatusNotImplemented) 140 return 141 } 142 serv.paused = !serv.paused 143 serv.TogglePause(serv.paused) 144 } 145 http.Redirect(w, r, r.FormValue("url"), http.StatusFound) 146 } 147 148 func (serv *HTTPServer) httpMain(w http.ResponseWriter, r *http.Request) { 149 data := &UISummaryData{ 150 UIPageHeader: serv.pageHeader(r, "syzkaller"), 151 Log: log.CachedLogOutput(), 152 } 153 154 level := stat.Simple 155 if serv.expertMode { 156 level = stat.All 157 } 158 for _, stat := range stat.Collect(level) { 159 data.Stats = append(data.Stats, UIStat{ 160 Name: stat.Name, 161 Value: stat.Value, 162 Hint: stat.Desc, 163 Link: stat.Link, 164 }) 165 } 166 if serv.CrashStore != nil { 167 var err error 168 if data.Crashes, err = serv.collectCrashes(serv.Cfg.Workdir); err != nil { 169 http.Error(w, fmt.Sprintf("failed to collect crashes: %v", err), http.StatusInternalServerError) 170 return 171 } 172 } 173 if serv.DiffStore != nil { 174 data.PatchedOnly, data.AffectsBoth, data.InProgress = serv.collectDiffCrashes() 175 } 176 executeTemplate(w, mainTemplate, data) 177 } 178 179 func (serv *HTTPServer) httpConfig(w http.ResponseWriter, r *http.Request) { 180 serv.jsonPage(w, r, "config", serv.Cfg) 181 } 182 183 func (serv *HTTPServer) jsonPage(w http.ResponseWriter, r *http.Request, title string, data any) { 184 text, err := json.MarshalIndent(data, "", "\t") 185 if err != nil { 186 http.Error(w, fmt.Sprintf("failed to encode json: %v", err), http.StatusInternalServerError) 187 return 188 } 189 serv.textPage(w, r, title, text) 190 } 191 192 func (serv *HTTPServer) textPage(w http.ResponseWriter, r *http.Request, title string, text []byte) { 193 if r.FormValue("raw") != "" { 194 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 195 w.Write(text) 196 return 197 } 198 data := &UITextPage{ 199 UIPageHeader: serv.pageHeader(r, title), 200 Text: text, 201 } 202 executeTemplate(w, textTemplate, data) 203 } 204 205 func (serv *HTTPServer) httpSyscalls(w http.ResponseWriter, r *http.Request) { 206 var calls map[string]*corpus.CallCov 207 total := make(map[string]int) 208 fuzzerObj := serv.Fuzzer.Load() 209 syscallsObj := serv.EnabledSyscalls.Load() 210 corpusObj := serv.Corpus.Load() 211 if corpusObj != nil && syscallsObj != nil { 212 calls = corpusObj.CallCover() 213 // Add enabled, but not yet covered calls. 214 for call := range syscallsObj.(map[*prog.Syscall]bool) { 215 if calls[call.Name] == nil { 216 calls[call.Name] = new(corpus.CallCov) 217 } 218 } 219 // Count number of programs that include each call. 220 last := make(map[string]*prog.Prog) 221 for _, inp := range corpusObj.Items() { 222 for _, call := range inp.Prog.Calls { 223 name := call.Meta.Name 224 if last[name] != inp.Prog { 225 total[name]++ 226 } 227 last[name] = inp.Prog 228 } 229 } 230 } 231 data := &UISyscallsData{ 232 UIPageHeader: serv.pageHeader(r, "syscalls"), 233 } 234 for c, cc := range calls { 235 var syscallID *int 236 if syscall, ok := serv.Cfg.Target.SyscallMap[c]; ok { 237 syscallID = &syscall.ID 238 } 239 coverOverflows, compsOverflows := 0, 0 240 if fuzzerObj != nil { 241 idx := len(serv.Cfg.Target.Syscalls) 242 if c != prog.ExtraCallName { 243 idx = serv.Cfg.Target.SyscallMap[c].ID 244 } 245 coverOverflows = int(fuzzerObj.Syscalls[idx].CoverOverflows.Load()) 246 compsOverflows = int(fuzzerObj.Syscalls[idx].CompsOverflows.Load()) 247 } 248 data.Calls = append(data.Calls, UICallType{ 249 Name: c, 250 ID: syscallID, 251 Inputs: cc.Count, 252 Total: total[c], 253 Cover: len(cc.Cover), 254 CoverOverflows: coverOverflows, 255 CompsOverflows: compsOverflows, 256 }) 257 } 258 sort.Slice(data.Calls, func(i, j int) bool { 259 return data.Calls[i].Name < data.Calls[j].Name 260 }) 261 executeTemplate(w, syscallsTemplate, data) 262 } 263 264 func (serv *HTTPServer) httpStats(w http.ResponseWriter, r *http.Request) { 265 html, err := pages.StatsHTML() 266 if err != nil { 267 http.Error(w, err.Error(), http.StatusInternalServerError) 268 return 269 } 270 data := &UITextPage{ 271 UIPageHeader: serv.pageHeader(r, "stats"), 272 HTML: html, 273 } 274 executeTemplate(w, textTemplate, data) 275 } 276 277 func (serv *HTTPServer) httpVMs(w http.ResponseWriter, r *http.Request) { 278 pool := serv.Pools[r.FormValue("pool")] 279 if pool == nil { 280 http.Error(w, "no such VM pool is known (yet)", http.StatusInternalServerError) 281 return 282 } 283 data := &UIVMData{ 284 UIPageHeader: serv.pageHeader(r, "VMs"), 285 } 286 // TODO: we could also query vmLoop for VMs that are idle (waiting to start reproducing), 287 // and query the exact bug that is being reproduced by a VM. 288 for id, state := range pool.State() { 289 name := fmt.Sprintf("#%d", id) 290 info := UIVMInfo{ 291 Name: name, 292 State: "unknown", 293 Since: time.Since(state.LastUpdate), 294 } 295 switch state.State { 296 case dispatcher.StateOffline: 297 info.State = "offline" 298 case dispatcher.StateBooting: 299 info.State = "booting" 300 case dispatcher.StateWaiting: 301 info.State = "waiting" 302 case dispatcher.StateRunning: 303 info.State = "running: " + state.Status 304 } 305 if state.Reserved { 306 info.State = "[reserved] " + info.State 307 } 308 if state.MachineInfo != nil { 309 info.MachineInfo = fmt.Sprintf("/vm?type=machine-info&id=%d", id) 310 } 311 if state.DetailedStatus != nil { 312 info.DetailedStatus = fmt.Sprintf("/vm?type=detailed-status&id=%v", id) 313 } 314 data.VMs = append(data.VMs, info) 315 } 316 executeTemplate(w, vmsTemplate, data) 317 } 318 319 func (serv *HTTPServer) httpVM(w http.ResponseWriter, r *http.Request) { 320 pool := serv.Pools[r.FormValue("pool")] 321 if pool == nil { 322 http.Error(w, "no such VM pool is known (yet)", http.StatusInternalServerError) 323 return 324 } 325 326 w.Header().Set("Content-Type", ctTextPlain) 327 id, err := strconv.Atoi(r.FormValue("id")) 328 infos := pool.State() 329 if err != nil || id < 0 || id >= len(infos) { 330 http.Error(w, "invalid instance id", http.StatusBadRequest) 331 return 332 } 333 info := infos[id] 334 switch r.FormValue("type") { 335 case "machine-info": 336 if info.MachineInfo != nil { 337 w.Write(info.MachineInfo()) 338 } 339 case "detailed-status": 340 if info.DetailedStatus != nil { 341 w.Write(info.DetailedStatus()) 342 } 343 default: 344 w.Write([]byte("unknown info type")) 345 } 346 } 347 348 func makeUICrashType(info *BugInfo, startTime time.Time, repros map[string]bool) UICrashType { 349 var crashes []UICrash 350 for _, crash := range info.Crashes { 351 crashes = append(crashes, UICrash{ 352 CrashInfo: *crash, 353 Active: crash.Time.After(startTime), 354 }) 355 } 356 triaged := reproStatus(info.HasRepro, info.HasCRepro, repros[info.Title], 357 info.ReproAttempts >= MaxReproAttempts) 358 return UICrashType{ 359 BugInfo: *info, 360 RankTooltip: higherRankTooltip(info.Title, info.TailTitles), 361 New: info.FirstTime.After(startTime), 362 Active: info.LastTime.After(startTime), 363 Triaged: triaged, 364 Crashes: crashes, 365 } 366 } 367 368 // higherRankTooltip generates the prioritized list of the titles with higher Rank 369 // than the firstTitle has. 370 func higherRankTooltip(firstTitle string, titlesInfo []*report.TitleFreqRank) string { 371 baseRank := report.TitlesToImpact(firstTitle) 372 res := "" 373 for _, ti := range titlesInfo { 374 if ti.Rank <= baseRank { 375 continue 376 } 377 res += fmt.Sprintf("[rank %2v, freq %5.1f%%] %s\n", 378 ti.Rank, 379 100*float32(ti.Count)/float32(ti.Total), 380 ti.Title) 381 } 382 if res != "" { 383 return fmt.Sprintf("[rank %2v, originally] %s\n%s", baseRank, firstTitle, res) 384 } 385 return res 386 } 387 388 var crashIDRe = regexp.MustCompile(`^\w+$`) 389 390 func (serv *HTTPServer) httpCrash(w http.ResponseWriter, r *http.Request) { 391 crashID := r.FormValue("id") 392 if !crashIDRe.MatchString(crashID) { 393 http.Error(w, "invalid crash ID", http.StatusBadRequest) 394 return 395 } 396 info, err := serv.CrashStore.BugInfo(crashID, true) 397 if err != nil { 398 http.Error(w, "failed to read crash info", http.StatusInternalServerError) 399 return 400 } 401 data := UICrashPage{ 402 UIPageHeader: serv.pageHeader(r, info.Title), 403 UICrashType: makeUICrashType(info, serv.StartTime, nil), 404 } 405 executeTemplate(w, crashTemplate, data) 406 } 407 408 func (serv *HTTPServer) httpCorpus(w http.ResponseWriter, r *http.Request) { 409 corpus := serv.Corpus.Load() 410 if corpus == nil { 411 http.Error(w, "the corpus information is not yet available", http.StatusInternalServerError) 412 return 413 } 414 data := UICorpusPage{ 415 UIPageHeader: serv.pageHeader(r, "corpus"), 416 Call: r.FormValue("call"), 417 RawCover: serv.Cfg.RawCover, 418 } 419 for _, inp := range corpus.Items() { 420 if data.Call != "" && data.Call != inp.StringCall() { 421 continue 422 } 423 data.Inputs = append(data.Inputs, UIInput{ 424 Sig: inp.Sig, 425 Short: inp.Prog.String(), 426 Cover: len(inp.Cover), 427 }) 428 } 429 sort.Slice(data.Inputs, func(i, j int) bool { 430 a, b := data.Inputs[i], data.Inputs[j] 431 if a.Cover != b.Cover { 432 return a.Cover > b.Cover 433 } 434 return a.Short < b.Short 435 }) 436 executeTemplate(w, corpusTemplate, data) 437 } 438 439 func (serv *HTTPServer) httpDownloadCorpus(w http.ResponseWriter, r *http.Request) { 440 corpus := filepath.Join(serv.Cfg.Workdir, "corpus.db") 441 file, err := os.Open(corpus) 442 if err != nil { 443 http.Error(w, fmt.Sprintf("failed to open corpus : %v", err), http.StatusInternalServerError) 444 return 445 } 446 defer file.Close() 447 buf, err := io.ReadAll(file) 448 if err != nil { 449 http.Error(w, fmt.Sprintf("failed to read corpus : %v", err), http.StatusInternalServerError) 450 return 451 } 452 w.Write(buf) 453 } 454 455 const ( 456 DoHTML int = iota 457 DoSubsystemCover 458 DoModuleCover 459 DoFuncCover 460 DoFileCover 461 DoRawCoverFiles 462 DoRawCover 463 DoFilterPCs 464 DoCoverJSONL 465 DoCoverPrograms 466 ) 467 468 func (serv *HTTPServer) httpCover(w http.ResponseWriter, r *http.Request) { 469 if !serv.Cfg.Cover { 470 serv.httpCoverFallback(w, r) 471 return 472 } 473 if r.FormValue("jsonl") == "1" { 474 serv.httpCoverCover(w, r, DoCoverJSONL) 475 return 476 } 477 serv.httpCoverCover(w, r, DoHTML) 478 } 479 480 func (serv *HTTPServer) httpPrograms(w http.ResponseWriter, r *http.Request) { 481 if !serv.Cfg.Cover { 482 http.Error(w, "coverage is not enabled", http.StatusInternalServerError) 483 return 484 } 485 if r.FormValue("jsonl") != "1" { 486 http.Error(w, "only ?jsonl=1 param is supported", http.StatusBadRequest) 487 return 488 } 489 serv.httpCoverCover(w, r, DoCoverPrograms) 490 } 491 492 func (serv *HTTPServer) httpSubsystemCover(w http.ResponseWriter, r *http.Request) { 493 if !serv.Cfg.Cover { 494 serv.httpCoverFallback(w, r) 495 return 496 } 497 serv.httpCoverCover(w, r, DoSubsystemCover) 498 } 499 500 func (serv *HTTPServer) httpModuleCover(w http.ResponseWriter, r *http.Request) { 501 if !serv.Cfg.Cover { 502 serv.httpCoverFallback(w, r) 503 return 504 } 505 serv.httpCoverCover(w, r, DoModuleCover) 506 } 507 508 const ctTextPlain = "text/plain; charset=utf-8" 509 const ctApplicationJSON = "application/json" 510 511 func (serv *HTTPServer) httpCoverCover(w http.ResponseWriter, r *http.Request, funcFlag int) { 512 if !serv.Cfg.Cover { 513 http.Error(w, "coverage is not enabled", http.StatusInternalServerError) 514 return 515 } 516 517 coverInfo := serv.Cover.Load() 518 if coverInfo == nil { 519 http.Error(w, "coverage is not ready, please try again later after fuzzer started", http.StatusInternalServerError) 520 return 521 } 522 523 corpus := serv.Corpus.Load() 524 if corpus == nil { 525 http.Error(w, "the corpus information is not yet available", http.StatusInternalServerError) 526 return 527 } 528 529 rg, err := coverInfo.ReportGenerator.Get() 530 if err != nil { 531 http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) 532 return 533 } 534 535 if r.FormValue("flush") != "" { 536 defer func() { 537 coverInfo.ReportGenerator.Reset() 538 debug.FreeOSMemory() 539 }() 540 } 541 542 var progs []coverProgRaw 543 if sig := r.FormValue("input"); sig != "" { 544 inp := corpus.Item(sig) 545 if inp == nil { 546 http.Error(w, "unknown input hash", http.StatusInternalServerError) 547 return 548 } 549 if r.FormValue("update_id") != "" { 550 updateID, err := strconv.Atoi(r.FormValue("update_id")) 551 if err != nil || updateID < 0 || updateID >= len(inp.Updates) { 552 http.Error(w, "bad call_id", http.StatusBadRequest) 553 return 554 } 555 progs = append(progs, coverProgRaw{ 556 sig: sig, 557 prog: inp.Prog, 558 pcs: CoverToPCs(serv.Cfg, inp.Updates[updateID].RawCover), 559 }) 560 } else { 561 progs = append(progs, coverProgRaw{ 562 sig: sig, 563 prog: inp.Prog, 564 pcs: CoverToPCs(serv.Cfg, inp.Cover), 565 }) 566 } 567 } else { 568 call := r.FormValue("call") 569 for _, inp := range corpus.Items() { 570 if call != "" && call != inp.StringCall() { 571 continue 572 } 573 progs = append(progs, coverProgRaw{ 574 sig: inp.Sig, 575 prog: inp.Prog, 576 pcs: CoverToPCs(serv.Cfg, inp.Cover), 577 }) 578 } 579 } 580 581 var coverFilter map[uint64]struct{} 582 if r.FormValue("filter") != "" || funcFlag == DoFilterPCs { 583 if coverInfo.CoverFilter == nil { 584 http.Error(w, "cover is not filtered in config", http.StatusInternalServerError) 585 return 586 } 587 coverFilter = coverInfo.CoverFilter 588 } 589 590 params := cover.HandlerParams{ 591 Progs: serv.serializeCoverProgs(progs), 592 Filter: coverFilter, 593 Debug: r.FormValue("debug") != "", 594 Force: r.FormValue("force") != "", 595 } 596 597 type handlerFuncType func(w io.Writer, params cover.HandlerParams) error 598 flagToFunc := map[int]struct { 599 Do handlerFuncType 600 contentType string 601 }{ 602 DoHTML: {rg.DoHTML, ""}, 603 DoSubsystemCover: {rg.DoSubsystemCover, ""}, 604 DoModuleCover: {rg.DoModuleCover, ""}, 605 DoFuncCover: {rg.DoFuncCover, ctTextPlain}, 606 DoFileCover: {rg.DoFileCover, ctTextPlain}, 607 DoRawCoverFiles: {rg.DoRawCoverFiles, ctTextPlain}, 608 DoRawCover: {rg.DoRawCover, ctTextPlain}, 609 DoFilterPCs: {rg.DoFilterPCs, ctTextPlain}, 610 DoCoverJSONL: {rg.DoCoverJSONL, ctApplicationJSON}, 611 DoCoverPrograms: {rg.DoCoverPrograms, ctApplicationJSON}, 612 } 613 614 if ct := flagToFunc[funcFlag].contentType; ct != "" { 615 w.Header().Set("Content-Type", ct) 616 } 617 618 if err := flagToFunc[funcFlag].Do(w, params); err != nil { 619 http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) 620 return 621 } 622 } 623 624 type coverProgRaw struct { 625 sig string 626 prog *prog.Prog 627 pcs []uint64 628 } 629 630 // Once the total size of corpus programs exceeds 100MB, skip fs images from it. 631 const compactProgsCutOff = 100 * 1000 * 1000 632 633 func (serv *HTTPServer) serializeCoverProgs(rawProgs []coverProgRaw) []cover.Prog { 634 skipImages := false 635 outerLoop: 636 for { 637 var flags []prog.SerializeFlag 638 if skipImages { 639 flags = append(flags, prog.SkipImages) 640 } 641 totalSize := 0 642 var ret []cover.Prog 643 for _, item := range rawProgs { 644 prog := cover.Prog{ 645 Sig: item.sig, 646 Data: string(item.prog.Serialize(flags...)), 647 PCs: item.pcs, 648 } 649 totalSize += len(prog.Data) 650 if totalSize > compactProgsCutOff && !skipImages { 651 log.Logf(0, "total size of corpus programs is too big, "+ 652 "full fs image won't be included in the cover reports") 653 skipImages = true 654 continue outerLoop 655 } 656 ret = append(ret, prog) 657 } 658 return ret 659 } 660 } 661 662 func (serv *HTTPServer) httpCoverFallback(w http.ResponseWriter, r *http.Request) { 663 corpus := serv.Corpus.Load() 664 if corpus == nil { 665 http.Error(w, "the corpus information is not yet available", http.StatusInternalServerError) 666 return 667 } 668 calls := make(map[int][]int) 669 for s := range corpus.Signal() { 670 id, errno := prog.DecodeFallbackSignal(uint64(s)) 671 calls[id] = append(calls[id], errno) 672 } 673 data := &UIFallbackCoverData{ 674 UIPageHeader: serv.pageHeader(r, "fallback coverage"), 675 } 676 if obj := serv.EnabledSyscalls.Load(); obj != nil { 677 for call := range obj.(map[*prog.Syscall]bool) { 678 errnos := calls[call.ID] 679 sort.Ints(errnos) 680 successful := 0 681 for len(errnos) != 0 && errnos[0] == 0 { 682 successful++ 683 errnos = errnos[1:] 684 } 685 data.Calls = append(data.Calls, UIFallbackCall{ 686 Name: call.Name, 687 Successful: successful, 688 Errnos: errnos, 689 }) 690 } 691 } 692 sort.Slice(data.Calls, func(i, j int) bool { 693 return data.Calls[i].Name < data.Calls[j].Name 694 }) 695 executeTemplate(w, fallbackCoverTemplate, data) 696 } 697 698 func (serv *HTTPServer) httpFuncCover(w http.ResponseWriter, r *http.Request) { 699 serv.httpCoverCover(w, r, DoFuncCover) 700 } 701 702 func (serv *HTTPServer) httpFileCover(w http.ResponseWriter, r *http.Request) { 703 serv.httpCoverCover(w, r, DoFileCover) 704 } 705 706 func (serv *HTTPServer) httpPrio(w http.ResponseWriter, r *http.Request) { 707 corpus := serv.Corpus.Load() 708 if corpus == nil { 709 http.Error(w, "the corpus information is not yet available", http.StatusInternalServerError) 710 return 711 } 712 713 callName := r.FormValue("call") 714 call := serv.Cfg.Target.SyscallMap[callName] 715 if call == nil { 716 http.Error(w, fmt.Sprintf("unknown call: %v", callName), http.StatusInternalServerError) 717 return 718 } 719 720 var progs []*prog.Prog 721 for _, inp := range corpus.Items() { 722 progs = append(progs, inp.Prog) 723 } 724 725 var enabled map[*prog.Syscall]bool 726 if obj := serv.EnabledSyscalls.Load(); obj != nil { 727 enabled = obj.(map[*prog.Syscall]bool) 728 } 729 prios, generatable := serv.Cfg.Target.CalculatePriorities(progs, enabled) 730 731 data := &UIPrioData{ 732 UIPageHeader: serv.pageHeader(r, "syscall priorities"), 733 Call: callName, 734 } 735 for i, p := range prios[call.ID] { 736 syscall := serv.Cfg.Target.Syscalls[i] 737 if !generatable[syscall] { 738 continue 739 } 740 data.Prios = append(data.Prios, UIPrio{syscall.Name, p}) 741 } 742 sort.Slice(data.Prios, func(i, j int) bool { 743 return data.Prios[i].Prio > data.Prios[j].Prio 744 }) 745 executeTemplate(w, prioTemplate, data) 746 } 747 748 func (serv *HTTPServer) httpFile(w http.ResponseWriter, r *http.Request) { 749 file := filepath.Clean(r.FormValue("name")) 750 if !strings.HasPrefix(file, "crashes/") && !strings.HasPrefix(file, "corpus/") { 751 http.Error(w, "oh, oh, oh!", http.StatusInternalServerError) 752 return 753 } 754 data, err := os.ReadFile(filepath.Join(serv.Cfg.Workdir, file)) 755 if err != nil { 756 http.Error(w, "failed to read the file", http.StatusInternalServerError) 757 return 758 } 759 serv.textPage(w, r, "file", data) 760 } 761 762 func (serv *HTTPServer) httpInput(w http.ResponseWriter, r *http.Request) { 763 corpus := serv.Corpus.Load() 764 if corpus == nil { 765 http.Error(w, "the corpus information is not yet available", http.StatusInternalServerError) 766 return 767 } 768 inp := corpus.Item(r.FormValue("sig")) 769 if inp == nil { 770 http.Error(w, "can't find the input", http.StatusInternalServerError) 771 return 772 } 773 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 774 w.Write(inp.Prog.Serialize()) 775 } 776 777 func (serv *HTTPServer) httpDebugInput(w http.ResponseWriter, r *http.Request) { 778 corpus := serv.Corpus.Load() 779 if corpus == nil { 780 http.Error(w, "the corpus information is not yet available", http.StatusInternalServerError) 781 return 782 } 783 inp := corpus.Item(r.FormValue("sig")) 784 if inp == nil { 785 http.Error(w, "can't find the input", http.StatusInternalServerError) 786 return 787 } 788 getIDs := func(callID int) []int { 789 ret := []int{} 790 for id, update := range inp.Updates { 791 if update.Call == callID { 792 ret = append(ret, id) 793 } 794 } 795 return ret 796 } 797 var calls []UIRawCallCover 798 for pos, line := range strings.Split(string(inp.Prog.Serialize()), "\n") { 799 line = strings.TrimSpace(line) 800 if line == "" { 801 continue 802 } 803 calls = append(calls, UIRawCallCover{ 804 Sig: r.FormValue("sig"), 805 Call: line, 806 UpdateIDs: getIDs(pos), 807 }) 808 } 809 extraIDs := getIDs(-1) 810 if len(extraIDs) > 0 { 811 calls = append(calls, UIRawCallCover{ 812 Sig: r.FormValue("sig"), 813 Call: prog.ExtraCallName, 814 UpdateIDs: extraIDs, 815 }) 816 } 817 data := UIRawCoverPage{ 818 UIPageHeader: serv.pageHeader(r, "raw coverage"), 819 Calls: calls, 820 } 821 executeTemplate(w, rawCoverTemplate, data) 822 } 823 824 func (serv *HTTPServer) modulesInfo(w http.ResponseWriter, r *http.Request) { 825 cover := serv.Cover.Load() 826 if cover == nil { 827 http.Error(w, "info is not ready, please try again later after fuzzer started", http.StatusInternalServerError) 828 return 829 } 830 serv.jsonPage(w, r, "modules", cover.Modules) 831 } 832 833 func (serv *HTTPServer) httpAddCandidate(w http.ResponseWriter, r *http.Request) { 834 if r.Method != http.MethodPost { 835 http.Error(w, "only POST method supported", http.StatusMethodNotAllowed) 836 return 837 } 838 err := r.ParseMultipartForm(20 << 20) 839 if err != nil { 840 http.Error(w, fmt.Sprintf("failed to parse form: %v", err), http.StatusBadRequest) 841 return 842 } 843 file, _, err := r.FormFile("file") 844 if err != nil { 845 http.Error(w, fmt.Sprintf("failed to retrieve file from form-data: %v", err), http.StatusBadRequest) 846 return 847 } 848 defer file.Close() 849 data, err := io.ReadAll(file) 850 if err != nil { 851 http.Error(w, fmt.Sprintf("failed to read file: %v", err), http.StatusBadRequest) 852 return 853 } 854 prog, err := ParseSeed(serv.Cfg.Target, data) 855 if err != nil { 856 http.Error(w, fmt.Sprintf("failed to parse seed: %v", err), http.StatusBadRequest) 857 return 858 } 859 if !prog.OnlyContains(serv.Fuzzer.Load().Config.EnabledCalls) { 860 http.Error(w, "contains disabled syscall", http.StatusBadRequest) 861 return 862 } 863 var flags fuzzer.ProgFlags 864 flags |= fuzzer.ProgMinimized 865 flags |= fuzzer.ProgSmashed 866 candidates := []fuzzer.Candidate{{ 867 Prog: prog, 868 Flags: flags, 869 }} 870 serv.Fuzzer.Load().AddCandidates(candidates) 871 } 872 873 var alphaNumRegExp = regexp.MustCompile(`^[a-zA-Z0-9]*$`) 874 875 func isAlphanumeric(s string) bool { 876 return alphaNumRegExp.MatchString(s) 877 } 878 879 func (serv *HTTPServer) httpReport(w http.ResponseWriter, r *http.Request) { 880 crashID := r.FormValue("id") 881 if !isAlphanumeric(crashID) { 882 http.Error(w, "wrong id", http.StatusBadRequest) 883 return 884 } 885 886 info, err := serv.CrashStore.Report(crashID) 887 if err != nil { 888 http.Error(w, fmt.Sprintf("%v", err), http.StatusBadRequest) 889 return 890 } 891 892 commitDesc := "" 893 if info.Tag != "" { 894 commitDesc = fmt.Sprintf(" on commit %s.", info.Tag) 895 } 896 fmt.Fprintf(w, "Syzkaller hit '%s' bug%s.\n\n", info.Title, commitDesc) 897 if len(info.Report) != 0 { 898 fmt.Fprintf(w, "%s\n\n", info.Report) 899 } 900 if len(info.Prog) == 0 && len(info.CProg) == 0 { 901 fmt.Fprintf(w, "The bug is not reproducible.\n") 902 } else { 903 fmt.Fprintf(w, "Syzkaller reproducer:\n%s\n\n", info.Prog) 904 if len(info.CProg) != 0 { 905 fmt.Fprintf(w, "C reproducer:\n%s\n\n", info.CProg) 906 } 907 } 908 } 909 910 func (serv *HTTPServer) httpRawCover(w http.ResponseWriter, r *http.Request) { 911 serv.httpCoverCover(w, r, DoRawCover) 912 } 913 914 func (serv *HTTPServer) httpRawCoverFiles(w http.ResponseWriter, r *http.Request) { 915 serv.httpCoverCover(w, r, DoRawCoverFiles) 916 } 917 918 func (serv *HTTPServer) httpFilterPCs(w http.ResponseWriter, r *http.Request) { 919 serv.httpCoverCover(w, r, DoFilterPCs) 920 } 921 922 func (serv *HTTPServer) collectDiffCrashes() (patchedOnly, both, inProgress *UIDiffTable) { 923 for _, item := range serv.allDiffCrashes() { 924 if item.PatchedOnly() { 925 if patchedOnly == nil { 926 patchedOnly = &UIDiffTable{Title: "Patched-only"} 927 } 928 patchedOnly.List = append(patchedOnly.List, item) 929 } else if item.AffectsBoth() { 930 if both == nil { 931 both = &UIDiffTable{Title: "Affects both"} 932 } 933 both.List = append(both.List, item) 934 } else { 935 if inProgress == nil { 936 inProgress = &UIDiffTable{Title: "In Progress"} 937 } 938 inProgress.List = append(inProgress.List, item) 939 } 940 } 941 return 942 } 943 944 func (serv *HTTPServer) allDiffCrashes() []UIDiffBug { 945 repros := serv.ReproLoop.Reproducing() 946 var list []UIDiffBug 947 for _, bug := range serv.DiffStore.List() { 948 list = append(list, UIDiffBug{ 949 DiffBug: bug, 950 Reproducing: repros[bug.Title], 951 }) 952 } 953 sort.Slice(list, func(i, j int) bool { 954 first, second := list[i], list[j] 955 firstPatched, secondPatched := first.PatchedOnly(), second.PatchedOnly() 956 if firstPatched != secondPatched { 957 return firstPatched 958 } 959 return first.Title < second.Title 960 }) 961 return list 962 } 963 964 func (serv *HTTPServer) collectCrashes(workdir string) ([]UICrashType, error) { 965 list, err := serv.CrashStore.BugList() 966 if err != nil { 967 return nil, err 968 } 969 repros := serv.ReproLoop.Reproducing() 970 var ret []UICrashType 971 for _, info := range list { 972 ret = append(ret, makeUICrashType(info, serv.StartTime, repros)) 973 } 974 return ret, nil 975 } 976 977 func (serv *HTTPServer) httpJobs(w http.ResponseWriter, r *http.Request) { 978 var list []*fuzzer.JobInfo 979 if fuzzer := serv.Fuzzer.Load(); fuzzer != nil { 980 list = fuzzer.RunningJobs() 981 } 982 if key := r.FormValue("id"); key != "" { 983 for _, item := range list { 984 if item.ID() == key { 985 w.Write(item.Bytes()) 986 return 987 } 988 } 989 http.Error(w, "invalid job id (the job has likely already finished)", http.StatusBadRequest) 990 return 991 } 992 jobType := r.FormValue("type") 993 data := UIJobList{ 994 UIPageHeader: serv.pageHeader(r, fmt.Sprintf("%s jobs", jobType)), 995 } 996 switch jobType { 997 case "triage": 998 case "smash": 999 case "hints": 1000 default: 1001 http.Error(w, "unknown job type", http.StatusBadRequest) 1002 return 1003 } 1004 for _, item := range list { 1005 if item.Type != jobType { 1006 continue 1007 } 1008 data.Jobs = append(data.Jobs, UIJobInfo{ 1009 ID: item.ID(), 1010 Short: item.Name, 1011 Execs: item.Execs.Load(), 1012 Calls: strings.Join(item.Calls, ", "), 1013 }) 1014 } 1015 sort.Slice(data.Jobs, func(i, j int) bool { 1016 a, b := data.Jobs[i], data.Jobs[j] 1017 return a.Short < b.Short 1018 }) 1019 executeTemplate(w, jobListTemplate, data) 1020 } 1021 1022 func reproStatus(hasRepro, hasCRepro, reproducing, nonReproducible bool) string { 1023 status := "" 1024 if hasRepro { 1025 status = "has repro" 1026 if hasCRepro { 1027 status = "has C repro" 1028 } 1029 } else if reproducing { 1030 status = "reproducing" 1031 } else if nonReproducible { 1032 status = "non-reproducible" 1033 } 1034 return status 1035 } 1036 1037 func executeTemplate(w http.ResponseWriter, templ *template.Template, data interface{}) { 1038 buf := new(bytes.Buffer) 1039 if err := templ.Execute(buf, data); err != nil { 1040 log.Logf(0, "failed to execute template: %v", err) 1041 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) 1042 return 1043 } 1044 w.Write(buf.Bytes()) 1045 } 1046 1047 type UISummaryData struct { 1048 UIPageHeader 1049 Stats []UIStat 1050 Crashes []UICrashType 1051 PatchedOnly *UIDiffTable 1052 AffectsBoth *UIDiffTable 1053 InProgress *UIDiffTable 1054 Log string 1055 } 1056 1057 type UIDiffTable struct { 1058 Title string 1059 List []UIDiffBug 1060 } 1061 1062 type UIVMData struct { 1063 UIPageHeader 1064 VMs []UIVMInfo 1065 } 1066 1067 type UIVMInfo struct { 1068 Name string 1069 State string 1070 Since time.Duration 1071 MachineInfo string 1072 DetailedStatus string 1073 } 1074 1075 type UISyscallsData struct { 1076 UIPageHeader 1077 Calls []UICallType 1078 } 1079 1080 type UICrashPage struct { 1081 UIPageHeader 1082 UICrashType 1083 } 1084 1085 type UICrashType struct { 1086 BugInfo 1087 RankTooltip string 1088 New bool // was first found in the current run 1089 Active bool // was found in the current run 1090 Triaged string 1091 Crashes []UICrash 1092 } 1093 1094 type UICrash struct { 1095 CrashInfo 1096 Active bool 1097 } 1098 1099 type UIDiffBug struct { 1100 DiffBug 1101 Reproducing bool 1102 } 1103 1104 type UIStat struct { 1105 Name string 1106 Value string 1107 Hint string 1108 Link string 1109 } 1110 1111 type UICallType struct { 1112 Name string 1113 ID *int 1114 Inputs int 1115 Total int 1116 Cover int 1117 CoverOverflows int 1118 CompsOverflows int 1119 } 1120 1121 type UICorpusPage struct { 1122 UIPageHeader 1123 Call string 1124 RawCover bool 1125 Inputs []UIInput 1126 } 1127 1128 type UIInput struct { 1129 Sig string 1130 Short string 1131 Cover int 1132 } 1133 1134 type UIPageHeader struct { 1135 Name string 1136 PageTitle string 1137 // Relative page URL w/o GET parameters (e.g. "/stats"). 1138 URLPath string 1139 // Relative page URL with GET parameters/fragment/etc (e.g. "/stats?foo=1#bar"). 1140 CurrentURL string 1141 // syzkaller build git revision and link. 1142 GitRevision string 1143 GitRevisionLink string 1144 ExpertMode bool 1145 Paused bool 1146 } 1147 1148 func (serv *HTTPServer) pageHeader(r *http.Request, title string) UIPageHeader { 1149 revision, revisionLink := prog.GitRevisionBase, "" 1150 if len(revision) > 8 { 1151 revisionLink = vcs.LogLink(vcs.SyzkallerRepo, revision) 1152 revision = revision[:8] 1153 } 1154 url := r.URL 1155 url.Scheme = "" 1156 url.Host = "" 1157 url.User = nil 1158 return UIPageHeader{ 1159 Name: serv.Cfg.Name, 1160 PageTitle: title, 1161 URLPath: r.URL.Path, 1162 CurrentURL: url.String(), 1163 GitRevision: revision, 1164 GitRevisionLink: revisionLink, 1165 ExpertMode: serv.expertMode, 1166 Paused: serv.paused, 1167 } 1168 } 1169 1170 func createPage(name string, data any) *template.Template { 1171 templ := pages.Create(fmt.Sprintf(string(mustReadHTML("common")), mustReadHTML(name))) 1172 templTypes = append(templTypes, templType{ 1173 templ: templ, 1174 data: data, 1175 }) 1176 return templ 1177 } 1178 1179 type templType struct { 1180 templ *template.Template 1181 data any 1182 } 1183 1184 var templTypes []templType 1185 1186 type UIPrioData struct { 1187 UIPageHeader 1188 Call string 1189 Prios []UIPrio 1190 } 1191 1192 type UIPrio struct { 1193 Call string 1194 Prio int32 1195 } 1196 1197 type UIFallbackCoverData struct { 1198 UIPageHeader 1199 Calls []UIFallbackCall 1200 } 1201 1202 type UIFallbackCall struct { 1203 Name string 1204 Successful int 1205 Errnos []int 1206 } 1207 1208 type UIRawCoverPage struct { 1209 UIPageHeader 1210 Calls []UIRawCallCover 1211 } 1212 1213 type UIRawCallCover struct { 1214 Sig string 1215 Call string 1216 UpdateIDs []int 1217 } 1218 1219 type UIJobList struct { 1220 UIPageHeader 1221 Jobs []UIJobInfo 1222 } 1223 1224 type UIJobInfo struct { 1225 ID string 1226 Short string 1227 Calls string 1228 Execs int32 1229 } 1230 1231 type UITextPage struct { 1232 UIPageHeader 1233 Text []byte 1234 HTML template.HTML 1235 } 1236 1237 var ( 1238 mainTemplate = createPage("main", UISummaryData{}) 1239 syscallsTemplate = createPage("syscalls", UISyscallsData{}) 1240 vmsTemplate = createPage("vms", UIVMData{}) 1241 crashTemplate = createPage("crash", UICrashPage{}) 1242 corpusTemplate = createPage("corpus", UICorpusPage{}) 1243 prioTemplate = createPage("prio", UIPrioData{}) 1244 fallbackCoverTemplate = createPage("fallback_cover", UIFallbackCoverData{}) 1245 rawCoverTemplate = createPage("raw_cover", UIRawCoverPage{}) 1246 jobListTemplate = createPage("job_list", UIJobList{}) 1247 textTemplate = createPage("text", UITextPage{}) 1248 ) 1249 1250 //go:embed html/*.html 1251 var htmlFiles embed.FS 1252 1253 func mustReadHTML(name string) []byte { 1254 data, err := htmlFiles.ReadFile("html/" + name + ".html") 1255 if err != nil { 1256 panic(err) 1257 } 1258 return data 1259 }