1// Copyright 2017 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 4package dash 5 6import ( 7 "bytes" 8 "compress/gzip" 9 "encoding/json" 10 "fmt" 11 "io/ioutil" 12 "net/http" 13 "reflect" 14 "sort" 15 "strings" 16 "time" 17 "unicode/utf8" 18 19 "github.com/google/syzkaller/dashboard/dashapi" 20 "github.com/google/syzkaller/pkg/email" 21 "github.com/google/syzkaller/pkg/hash" 22 "golang.org/x/net/context" 23 "google.golang.org/appengine" 24 "google.golang.org/appengine/datastore" 25 "google.golang.org/appengine/log" 26) 27 28func initAPIHandlers() { 29 http.Handle("/api", handleJSON(handleAPI)) 30} 31 32var apiHandlers = map[string]APIHandler{ 33 "log_error": apiLogError, 34 "job_poll": apiJobPoll, 35 "job_done": apiJobDone, 36 "reporting_poll_bugs": apiReportingPollBugs, 37 "reporting_poll_closed": apiReportingPollClosed, 38 "reporting_update": apiReportingUpdate, 39} 40 41var apiNamespaceHandlers = map[string]APINamespaceHandler{ 42 "upload_build": apiUploadBuild, 43 "builder_poll": apiBuilderPoll, 44 "report_build_error": apiReportBuildError, 45 "report_crash": apiReportCrash, 46 "report_failed_repro": apiReportFailedRepro, 47 "need_repro": apiNeedRepro, 48 "manager_stats": apiManagerStats, 49} 50 51type JSONHandler func(c context.Context, r *http.Request) (interface{}, error) 52type APIHandler func(c context.Context, r *http.Request, payload []byte) (interface{}, error) 53type APINamespaceHandler func(c context.Context, ns string, r *http.Request, payload []byte) (interface{}, error) 54 55const ( 56 maxReproPerBug = 10 57 reproRetryPeriod = 24 * time.Hour // try 1 repro per day until we have at least syz repro 58) 59 60// Overridable for testing. 61var timeNow = func(c context.Context) time.Time { 62 return time.Now() 63} 64 65func timeSince(c context.Context, t time.Time) time.Duration { 66 return timeNow(c).Sub(t) 67} 68 69func handleJSON(fn JSONHandler) http.Handler { 70 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 71 c := appengine.NewContext(r) 72 reply, err := fn(c, r) 73 if err != nil { 74 // ErrAccess is logged earlier. 75 if err != ErrAccess { 76 log.Errorf(c, "%v", err) 77 } 78 http.Error(w, err.Error(), http.StatusInternalServerError) 79 return 80 } 81 w.Header().Set("Content-Type", "application/json") 82 if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { 83 w.Header().Set("Content-Encoding", "gzip") 84 gz := gzip.NewWriter(w) 85 if err := json.NewEncoder(gz).Encode(reply); err != nil { 86 log.Errorf(c, "failed to encode reply: %v", err) 87 } 88 gz.Close() 89 } else { 90 if err := json.NewEncoder(w).Encode(reply); err != nil { 91 log.Errorf(c, "failed to encode reply: %v", err) 92 } 93 } 94 }) 95} 96 97func handleAPI(c context.Context, r *http.Request) (reply interface{}, err error) { 98 client := r.PostFormValue("client") 99 method := r.PostFormValue("method") 100 log.Infof(c, "api %q from %q", method, client) 101 ns, err := checkClient(c, client, r.PostFormValue("key")) 102 if err != nil { 103 if client != "" { 104 log.Errorf(c, "%v", err) 105 } else { 106 // Don't log as error if somebody just invokes /api. 107 log.Infof(c, "%v", err) 108 } 109 return nil, err 110 } 111 var payload []byte 112 if str := r.PostFormValue("payload"); str != "" { 113 gr, err := gzip.NewReader(strings.NewReader(str)) 114 if err != nil { 115 return nil, fmt.Errorf("failed to ungzip payload: %v", err) 116 } 117 payload, err = ioutil.ReadAll(gr) 118 if err != nil { 119 return nil, fmt.Errorf("failed to ungzip payload: %v", err) 120 } 121 if err := gr.Close(); err != nil { 122 return nil, fmt.Errorf("failed to ungzip payload: %v", err) 123 } 124 } 125 handler := apiHandlers[method] 126 if handler != nil { 127 return handler(c, r, payload) 128 } 129 nsHandler := apiNamespaceHandlers[method] 130 if nsHandler == nil { 131 return nil, fmt.Errorf("unknown api method %q", method) 132 } 133 if ns == "" { 134 return nil, fmt.Errorf("method %q must be called within a namespace", method) 135 } 136 return nsHandler(c, ns, r, payload) 137} 138 139func checkClient(c context.Context, name0, key0 string) (string, error) { 140 for name, key := range config.Clients { 141 if name == name0 { 142 if key != key0 { 143 return "", ErrAccess 144 } 145 return "", nil 146 } 147 } 148 for ns, cfg := range config.Namespaces { 149 for name, key := range cfg.Clients { 150 if name == name0 { 151 if key != key0 { 152 return "", ErrAccess 153 } 154 return ns, nil 155 } 156 } 157 } 158 return "", ErrAccess 159} 160 161func apiLogError(c context.Context, r *http.Request, payload []byte) (interface{}, error) { 162 req := new(dashapi.LogEntry) 163 if err := json.Unmarshal(payload, req); err != nil { 164 return nil, fmt.Errorf("failed to unmarshal request: %v", err) 165 } 166 log.Errorf(c, "%v: %v", req.Name, req.Text) 167 return nil, nil 168} 169 170func apiBuilderPoll(c context.Context, ns string, r *http.Request, payload []byte) (interface{}, error) { 171 req := new(dashapi.BuilderPollReq) 172 if err := json.Unmarshal(payload, req); err != nil { 173 return nil, fmt.Errorf("failed to unmarshal request: %v", err) 174 } 175 var bugs []*Bug 176 _, err := datastore.NewQuery("Bug"). 177 Filter("Namespace=", ns). 178 Filter("Status<", BugStatusFixed). 179 GetAll(c, &bugs) 180 if err != nil { 181 return nil, fmt.Errorf("failed to query bugs: %v", err) 182 } 183 m := make(map[string]bool) 184loop: 185 for _, bug := range bugs { 186 // TODO(dvyukov): include this condition into the query if possible. 187 if len(bug.Commits) == 0 { 188 continue 189 } 190 for _, mgr := range bug.PatchedOn { 191 if mgr == req.Manager { 192 continue loop 193 } 194 } 195 for _, com := range bug.Commits { 196 m[com] = true 197 } 198 } 199 commits := make([]string, 0, len(m)) 200 for com := range m { 201 commits = append(commits, com) 202 } 203 sort.Strings(commits) 204 reportEmail := "" 205 for _, reporting := range config.Namespaces[ns].Reporting { 206 if _, ok := reporting.Config.(*EmailConfig); ok { 207 reportEmail = ownEmail(c) 208 break 209 } 210 } 211 resp := &dashapi.BuilderPollResp{ 212 PendingCommits: commits, 213 ReportEmail: reportEmail, 214 } 215 return resp, nil 216} 217 218func apiJobPoll(c context.Context, r *http.Request, payload []byte) (interface{}, error) { 219 req := new(dashapi.JobPollReq) 220 if err := json.Unmarshal(payload, req); err != nil { 221 return nil, fmt.Errorf("failed to unmarshal request: %v", err) 222 } 223 if len(req.Managers) == 0 { 224 return nil, fmt.Errorf("no managers") 225 } 226 return pollPendingJobs(c, req.Managers) 227} 228 229func apiJobDone(c context.Context, r *http.Request, payload []byte) (interface{}, error) { 230 req := new(dashapi.JobDoneReq) 231 if err := json.Unmarshal(payload, req); err != nil { 232 return nil, fmt.Errorf("failed to unmarshal request: %v", err) 233 } 234 err := doneJob(c, req) 235 return nil, err 236} 237 238func apiUploadBuild(c context.Context, ns string, r *http.Request, payload []byte) (interface{}, error) { 239 req := new(dashapi.Build) 240 if err := json.Unmarshal(payload, req); err != nil { 241 return nil, fmt.Errorf("failed to unmarshal request: %v", err) 242 } 243 now := timeNow(c) 244 isNewBuild, err := uploadBuild(c, now, ns, req, BuildNormal) 245 if err != nil { 246 return nil, err 247 } 248 if len(req.Commits) != 0 || len(req.FixCommits) != 0 { 249 if err := addCommitsToBugs(c, ns, req.Manager, req.Commits, req.FixCommits); err != nil { 250 return nil, err 251 } 252 } 253 if isNewBuild { 254 if err := updateManager(c, ns, req.Manager, func(mgr *Manager, stats *ManagerStats) { 255 mgr.CurrentBuild = req.ID 256 mgr.FailedBuildBug = "" 257 }); err != nil { 258 return nil, err 259 } 260 } 261 return nil, nil 262} 263 264func uploadBuild(c context.Context, now time.Time, ns string, req *dashapi.Build, typ BuildType) (bool, error) { 265 if _, err := loadBuild(c, ns, req.ID); err == nil { 266 return false, nil 267 } 268 269 checkStrLen := func(str, name string, maxLen int) error { 270 if str == "" { 271 return fmt.Errorf("%v is empty", name) 272 } 273 if len(str) > maxLen { 274 return fmt.Errorf("%v is too long (%v)", name, len(str)) 275 } 276 return nil 277 } 278 if err := checkStrLen(req.Manager, "Build.Manager", MaxStringLen); err != nil { 279 return false, err 280 } 281 if err := checkStrLen(req.ID, "Build.ID", MaxStringLen); err != nil { 282 return false, err 283 } 284 if err := checkStrLen(req.KernelRepo, "Build.KernelRepo", MaxStringLen); err != nil { 285 return false, err 286 } 287 if len(req.KernelBranch) > MaxStringLen { 288 return false, fmt.Errorf("Build.KernelBranch is too long (%v)", len(req.KernelBranch)) 289 } 290 if err := checkStrLen(req.SyzkallerCommit, "Build.SyzkallerCommit", MaxStringLen); err != nil { 291 return false, err 292 } 293 if len(req.CompilerID) > MaxStringLen { 294 return false, fmt.Errorf("Build.CompilerID is too long (%v)", len(req.CompilerID)) 295 } 296 if err := checkStrLen(req.KernelCommit, "Build.KernelCommit", MaxStringLen); err != nil { 297 return false, err 298 } 299 configID, err := putText(c, ns, textKernelConfig, req.KernelConfig, true) 300 if err != nil { 301 return false, err 302 } 303 build := &Build{ 304 Namespace: ns, 305 Manager: req.Manager, 306 ID: req.ID, 307 Type: typ, 308 Time: now, 309 OS: req.OS, 310 Arch: req.Arch, 311 VMArch: req.VMArch, 312 SyzkallerCommit: req.SyzkallerCommit, 313 CompilerID: req.CompilerID, 314 KernelRepo: req.KernelRepo, 315 KernelBranch: req.KernelBranch, 316 KernelCommit: req.KernelCommit, 317 KernelCommitTitle: req.KernelCommitTitle, 318 KernelCommitDate: req.KernelCommitDate, 319 KernelConfig: configID, 320 } 321 if _, err := datastore.Put(c, buildKey(c, ns, req.ID), build); err != nil { 322 return false, err 323 } 324 return true, nil 325} 326 327func addCommitsToBugs(c context.Context, ns, manager string, 328 titles []string, fixCommits []dashapi.FixCommit) error { 329 presentCommits := make(map[string]bool) 330 bugFixedBy := make(map[string][]string) 331 for _, com := range titles { 332 presentCommits[com] = true 333 } 334 for _, com := range fixCommits { 335 presentCommits[com.Title] = true 336 bugFixedBy[com.BugID] = append(bugFixedBy[com.BugID], com.Title) 337 } 338 managers, err := managerList(c, ns) 339 if err != nil { 340 return err 341 } 342 var bugs []*Bug 343 _, err = datastore.NewQuery("Bug"). 344 Filter("Namespace=", ns). 345 GetAll(c, &bugs) 346 if err != nil { 347 return fmt.Errorf("failed to query bugs: %v", err) 348 } 349nextBug: 350 for _, bug := range bugs { 351 switch bug.Status { 352 case BugStatusOpen, BugStatusDup: 353 case BugStatusFixed, BugStatusInvalid: 354 continue nextBug 355 default: 356 return fmt.Errorf("addCommitsToBugs: unknown bug status %v", bug.Status) 357 } 358 var fixCommits []string 359 for i := range bug.Reporting { 360 fixCommits = append(fixCommits, bugFixedBy[bug.Reporting[i].ID]...) 361 } 362 sort.Strings(fixCommits) 363 if err := addCommitsToBug(c, bug, manager, managers, fixCommits, presentCommits); err != nil { 364 return err 365 } 366 if bug.Status == BugStatusDup { 367 canon, err := canonicalBug(c, bug) 368 if err != nil { 369 return err 370 } 371 if canon.Status == BugStatusOpen && len(bug.Commits) == 0 { 372 if err := addCommitsToBug(c, canon, manager, managers, 373 fixCommits, presentCommits); err != nil { 374 return err 375 } 376 } 377 } 378 } 379 return nil 380} 381 382func addCommitsToBug(c context.Context, bug *Bug, manager string, managers []string, 383 fixCommits []string, presentCommits map[string]bool) error { 384 if !bugNeedsCommitUpdate(c, bug, manager, fixCommits, presentCommits, true) { 385 return nil 386 } 387 now := timeNow(c) 388 bugKey := datastore.NewKey(c, "Bug", bugKeyHash(bug.Namespace, bug.Title, bug.Seq), 0, nil) 389 tx := func(c context.Context) error { 390 bug := new(Bug) 391 if err := datastore.Get(c, bugKey, bug); err != nil { 392 return fmt.Errorf("failed to get bug %v: %v", bugKey.StringID(), err) 393 } 394 if !bugNeedsCommitUpdate(c, bug, manager, fixCommits, presentCommits, false) { 395 return nil 396 } 397 if len(fixCommits) != 0 && !reflect.DeepEqual(bug.Commits, fixCommits) { 398 bug.Commits = fixCommits 399 bug.PatchedOn = nil 400 } 401 bug.PatchedOn = append(bug.PatchedOn, manager) 402 if bug.Status == BugStatusOpen { 403 fixed := true 404 for _, mgr := range managers { 405 if !stringInList(bug.PatchedOn, mgr) { 406 fixed = false 407 break 408 } 409 } 410 if fixed { 411 bug.Status = BugStatusFixed 412 bug.Closed = now 413 } 414 } 415 if _, err := datastore.Put(c, bugKey, bug); err != nil { 416 return fmt.Errorf("failed to put bug: %v", err) 417 } 418 return nil 419 } 420 return datastore.RunInTransaction(c, tx, nil) 421} 422 423func managerList(c context.Context, ns string) ([]string, error) { 424 var builds []*Build 425 _, err := datastore.NewQuery("Build"). 426 Filter("Namespace=", ns). 427 Project("Manager"). 428 Distinct(). 429 GetAll(c, &builds) 430 if err != nil { 431 return nil, fmt.Errorf("failed to query builds: %v", err) 432 } 433 configManagers := config.Namespaces[ns].Managers 434 var managers []string 435 for _, build := range builds { 436 if configManagers[build.Manager].Decommissioned { 437 continue 438 } 439 managers = append(managers, build.Manager) 440 } 441 return managers, nil 442} 443 444func bugNeedsCommitUpdate(c context.Context, bug *Bug, manager string, fixCommits []string, 445 presentCommits map[string]bool, dolog bool) bool { 446 if len(fixCommits) != 0 && !reflect.DeepEqual(bug.Commits, fixCommits) { 447 if dolog { 448 log.Infof(c, "bug %q is fixed with %q", bug.Title, fixCommits) 449 } 450 return true 451 } 452 if len(bug.Commits) == 0 || stringInList(bug.PatchedOn, manager) { 453 return false 454 } 455 for _, com := range bug.Commits { 456 if !presentCommits[com] { 457 return false 458 } 459 } 460 return true 461} 462 463func stringInList(list []string, str string) bool { 464 for _, s := range list { 465 if s == str { 466 return true 467 } 468 } 469 return false 470} 471 472func stringsInList(list, str []string) bool { 473 for _, s := range str { 474 if !stringInList(list, s) { 475 return false 476 } 477 } 478 return true 479} 480 481func apiReportBuildError(c context.Context, ns string, r *http.Request, payload []byte) (interface{}, error) { 482 req := new(dashapi.BuildErrorReq) 483 if err := json.Unmarshal(payload, req); err != nil { 484 return nil, fmt.Errorf("failed to unmarshal request: %v", err) 485 } 486 now := timeNow(c) 487 if _, err := uploadBuild(c, now, ns, &req.Build, BuildFailed); err != nil { 488 return nil, err 489 } 490 req.Crash.BuildID = req.Build.ID 491 bug, err := reportCrash(c, ns, &req.Crash) 492 if err != nil { 493 return nil, err 494 } 495 if err := updateManager(c, ns, req.Build.Manager, func(mgr *Manager, stats *ManagerStats) { 496 mgr.FailedBuildBug = bugKeyHash(bug.Namespace, bug.Title, bug.Seq) 497 }); err != nil { 498 return nil, err 499 } 500 return nil, nil 501} 502 503const corruptedReportTitle = "corrupted report" 504 505func apiReportCrash(c context.Context, ns string, r *http.Request, payload []byte) (interface{}, error) { 506 req := new(dashapi.Crash) 507 if err := json.Unmarshal(payload, req); err != nil { 508 return nil, fmt.Errorf("failed to unmarshal request: %v", err) 509 } 510 bug, err := reportCrash(c, ns, req) 511 if err != nil { 512 return nil, err 513 } 514 resp := &dashapi.ReportCrashResp{ 515 NeedRepro: needRepro(c, bug), 516 } 517 return resp, nil 518} 519 520func reportCrash(c context.Context, ns string, req *dashapi.Crash) (*Bug, error) { 521 req.Title = limitLength(req.Title, maxTextLen) 522 req.Maintainers = email.MergeEmailLists(req.Maintainers) 523 if req.Corrupted { 524 // The report is corrupted and the title is most likely invalid. 525 // Such reports are usually unactionable and are discarded. 526 // Collect them into a single bin. 527 req.Title = corruptedReportTitle 528 } 529 530 bug, bugKey, err := findBugForCrash(c, ns, req.Title) 531 if err != nil { 532 return nil, err 533 } 534 if active, err := isActiveBug(c, bug); err != nil { 535 return nil, err 536 } else if !active { 537 bug, bugKey, err = createBugForCrash(c, ns, req) 538 if err != nil { 539 return nil, err 540 } 541 } 542 build, err := loadBuild(c, ns, req.BuildID) 543 if err != nil { 544 return nil, err 545 } 546 547 now := timeNow(c) 548 reproLevel := ReproLevelNone 549 if len(req.ReproC) != 0 { 550 reproLevel = ReproLevelC 551 } else if len(req.ReproSyz) != 0 { 552 reproLevel = ReproLevelSyz 553 } 554 save := reproLevel != ReproLevelNone || 555 bug.NumCrashes < maxCrashes || 556 now.Sub(bug.LastSavedCrash) > time.Hour || 557 bug.NumCrashes%20 == 0 558 if save { 559 if err := saveCrash(c, ns, req, bugKey, build); err != nil { 560 return nil, err 561 } 562 } else { 563 log.Infof(c, "not saving crash for %q", bug.Title) 564 } 565 566 tx := func(c context.Context) error { 567 bug = new(Bug) 568 if err := datastore.Get(c, bugKey, bug); err != nil { 569 return fmt.Errorf("failed to get bug: %v", err) 570 } 571 bug.NumCrashes++ 572 bug.LastTime = now 573 if save { 574 bug.LastSavedCrash = now 575 } 576 if reproLevel != ReproLevelNone { 577 bug.NumRepro++ 578 bug.LastReproTime = now 579 } 580 if bug.ReproLevel < reproLevel { 581 bug.ReproLevel = reproLevel 582 } 583 if len(req.Report) != 0 { 584 bug.HasReport = true 585 } 586 if !stringInList(bug.HappenedOn, build.Manager) { 587 bug.HappenedOn = append(bug.HappenedOn, build.Manager) 588 } 589 if _, err = datastore.Put(c, bugKey, bug); err != nil { 590 return fmt.Errorf("failed to put bug: %v", err) 591 } 592 return nil 593 } 594 if err := datastore.RunInTransaction(c, tx, &datastore.TransactionOptions{XG: true}); err != nil { 595 return nil, err 596 } 597 if save { 598 purgeOldCrashes(c, bug, bugKey) 599 } 600 return bug, nil 601} 602 603func saveCrash(c context.Context, ns string, req *dashapi.Crash, bugKey *datastore.Key, build *Build) error { 604 // Reporting priority of this crash. 605 prio := int64(kernelRepoInfo(build).ReportingPriority) * 1e6 606 if len(req.ReproC) != 0 { 607 prio += 4e12 608 } else if len(req.ReproSyz) != 0 { 609 prio += 2e12 610 } 611 if build.Arch == "amd64" { 612 prio += 1e3 613 } 614 crash := &Crash{ 615 Manager: build.Manager, 616 BuildID: req.BuildID, 617 Time: timeNow(c), 618 Maintainers: req.Maintainers, 619 ReproOpts: req.ReproOpts, 620 ReportLen: prio, 621 } 622 var err error 623 if crash.Log, err = putText(c, ns, textCrashLog, req.Log, false); err != nil { 624 return err 625 } 626 if crash.Report, err = putText(c, ns, textCrashReport, req.Report, false); err != nil { 627 return err 628 } 629 if crash.ReproSyz, err = putText(c, ns, textReproSyz, req.ReproSyz, false); err != nil { 630 return err 631 } 632 if crash.ReproC, err = putText(c, ns, textReproC, req.ReproC, false); err != nil { 633 return err 634 } 635 crashKey := datastore.NewIncompleteKey(c, "Crash", bugKey) 636 if _, err = datastore.Put(c, crashKey, crash); err != nil { 637 return fmt.Errorf("failed to put crash: %v", err) 638 } 639 return nil 640} 641 642func purgeOldCrashes(c context.Context, bug *Bug, bugKey *datastore.Key) { 643 if bug.NumCrashes <= maxCrashes || (bug.NumCrashes-1)%10 != 0 { 644 return 645 } 646 var crashes []*Crash 647 keys, err := datastore.NewQuery("Crash"). 648 Ancestor(bugKey). 649 Filter("ReproC=", 0). 650 Filter("ReproSyz=", 0). 651 Filter("Reported=", time.Time{}). 652 GetAll(c, &crashes) 653 if err != nil { 654 log.Errorf(c, "failed to fetch purge crashes: %v", err) 655 return 656 } 657 if len(keys) <= maxCrashes { 658 return 659 } 660 keyMap := make(map[*Crash]*datastore.Key) 661 for i, crash := range crashes { 662 keyMap[crash] = keys[i] 663 } 664 // Newest first. 665 sort.Slice(crashes, func(i, j int) bool { 666 return crashes[i].Time.After(crashes[j].Time) 667 }) 668 // Find latest crash on each manager. 669 latestOnManager := make(map[string]*Crash) 670 for _, crash := range crashes { 671 if latestOnManager[crash.Manager] == nil { 672 latestOnManager[crash.Manager] = crash 673 } 674 } 675 // Oldest first but move latest crash on each manager to the end (preserve them). 676 sort.Slice(crashes, func(i, j int) bool { 677 latesti := latestOnManager[crashes[i].Manager] == crashes[i] 678 latestj := latestOnManager[crashes[j].Manager] == crashes[j] 679 if latesti != latestj { 680 return latestj 681 } 682 return crashes[i].Time.Before(crashes[j].Time) 683 }) 684 crashes = crashes[:len(crashes)-maxCrashes] 685 var toDelete []*datastore.Key 686 for _, crash := range crashes { 687 if crash.ReproSyz != 0 || crash.ReproC != 0 || !crash.Reported.IsZero() { 688 log.Errorf(c, "purging reproducer?") 689 continue 690 } 691 toDelete = append(toDelete, keyMap[crash]) 692 if crash.Log != 0 { 693 toDelete = append(toDelete, datastore.NewKey(c, textCrashLog, "", crash.Log, nil)) 694 } 695 if crash.Report != 0 { 696 toDelete = append(toDelete, datastore.NewKey(c, textCrashReport, "", crash.Report, nil)) 697 } 698 } 699 if err := datastore.DeleteMulti(c, toDelete); err != nil { 700 log.Errorf(c, "failed to delete old crashes: %v", err) 701 return 702 } 703 log.Infof(c, "deleted %v crashes for bug %q", len(crashes), bug.Title) 704} 705 706func apiReportFailedRepro(c context.Context, ns string, r *http.Request, payload []byte) (interface{}, error) { 707 req := new(dashapi.CrashID) 708 if err := json.Unmarshal(payload, req); err != nil { 709 return nil, fmt.Errorf("failed to unmarshal request: %v", err) 710 } 711 req.Title = limitLength(req.Title, maxTextLen) 712 713 bug, bugKey, err := findBugForCrash(c, ns, req.Title) 714 if err != nil { 715 return nil, err 716 } 717 if bug == nil { 718 return nil, fmt.Errorf("%v: can't find bug for crash %q", ns, req.Title) 719 } 720 now := timeNow(c) 721 tx := func(c context.Context) error { 722 bug := new(Bug) 723 if err := datastore.Get(c, bugKey, bug); err != nil { 724 return fmt.Errorf("failed to get bug: %v", err) 725 } 726 bug.NumRepro++ 727 bug.LastReproTime = now 728 if _, err := datastore.Put(c, bugKey, bug); err != nil { 729 return fmt.Errorf("failed to put bug: %v", err) 730 } 731 return nil 732 } 733 err = datastore.RunInTransaction(c, tx, &datastore.TransactionOptions{ 734 XG: true, 735 Attempts: 30, 736 }) 737 return nil, err 738} 739 740func apiNeedRepro(c context.Context, ns string, r *http.Request, payload []byte) (interface{}, error) { 741 req := new(dashapi.CrashID) 742 if err := json.Unmarshal(payload, req); err != nil { 743 return nil, fmt.Errorf("failed to unmarshal request: %v", err) 744 } 745 if req.Corrupted { 746 resp := &dashapi.NeedReproResp{ 747 NeedRepro: false, 748 } 749 return resp, nil 750 } 751 req.Title = limitLength(req.Title, maxTextLen) 752 753 bug, _, err := findBugForCrash(c, ns, req.Title) 754 if err != nil { 755 return nil, err 756 } 757 if bug == nil { 758 return nil, fmt.Errorf("%v: can't find bug for crash %q", ns, req.Title) 759 } 760 resp := &dashapi.NeedReproResp{ 761 NeedRepro: needRepro(c, bug), 762 } 763 return resp, nil 764} 765 766func apiManagerStats(c context.Context, ns string, r *http.Request, payload []byte) (interface{}, error) { 767 req := new(dashapi.ManagerStatsReq) 768 if err := json.Unmarshal(payload, req); err != nil { 769 return nil, fmt.Errorf("failed to unmarshal request: %v", err) 770 } 771 now := timeNow(c) 772 err := updateManager(c, ns, req.Name, func(mgr *Manager, stats *ManagerStats) { 773 mgr.Link = req.Addr 774 mgr.LastAlive = now 775 mgr.CurrentUpTime = req.UpTime 776 if cur := int64(req.Corpus); cur > stats.MaxCorpus { 777 stats.MaxCorpus = cur 778 } 779 if cur := int64(req.Cover); cur > stats.MaxCover { 780 stats.MaxCover = cur 781 } 782 stats.TotalFuzzingTime += req.FuzzingTime 783 stats.TotalCrashes += int64(req.Crashes) 784 stats.TotalExecs += int64(req.Execs) 785 }) 786 return nil, err 787} 788 789func findBugForCrash(c context.Context, ns, title string) (*Bug, *datastore.Key, error) { 790 var bugs []*Bug 791 keys, err := datastore.NewQuery("Bug"). 792 Filter("Namespace=", ns). 793 Filter("Title=", title). 794 Order("-Seq"). 795 Limit(1). 796 GetAll(c, &bugs) 797 if err != nil { 798 return nil, nil, fmt.Errorf("failed to query bugs: %v", err) 799 } 800 if len(bugs) == 0 { 801 return nil, nil, nil 802 } 803 return bugs[0], keys[0], nil 804} 805 806func createBugForCrash(c context.Context, ns string, req *dashapi.Crash) (*Bug, *datastore.Key, error) { 807 var bug *Bug 808 var bugKey *datastore.Key 809 now := timeNow(c) 810 tx := func(c context.Context) error { 811 for seq := int64(0); ; seq++ { 812 bug = new(Bug) 813 bugHash := bugKeyHash(ns, req.Title, seq) 814 bugKey = datastore.NewKey(c, "Bug", bugHash, 0, nil) 815 if err := datastore.Get(c, bugKey, bug); err != nil { 816 if err != datastore.ErrNoSuchEntity { 817 return fmt.Errorf("failed to get bug: %v", err) 818 } 819 bug = &Bug{ 820 Namespace: ns, 821 Seq: seq, 822 Title: req.Title, 823 Status: BugStatusOpen, 824 NumCrashes: 0, 825 NumRepro: 0, 826 ReproLevel: ReproLevelNone, 827 HasReport: false, 828 FirstTime: now, 829 LastTime: now, 830 } 831 for _, rep := range config.Namespaces[ns].Reporting { 832 bug.Reporting = append(bug.Reporting, BugReporting{ 833 Name: rep.Name, 834 ID: bugReportingHash(bugHash, rep.Name), 835 }) 836 } 837 if bugKey, err = datastore.Put(c, bugKey, bug); err != nil { 838 return fmt.Errorf("failed to put new bug: %v", err) 839 } 840 return nil 841 } 842 canon, err := canonicalBug(c, bug) 843 if err != nil { 844 return err 845 } 846 if canon.Status != BugStatusOpen { 847 continue 848 } 849 return nil 850 } 851 } 852 if err := datastore.RunInTransaction(c, tx, &datastore.TransactionOptions{ 853 XG: true, 854 Attempts: 30, 855 }); err != nil { 856 return nil, nil, err 857 } 858 return bug, bugKey, nil 859} 860 861func isActiveBug(c context.Context, bug *Bug) (bool, error) { 862 if bug == nil { 863 return false, nil 864 } 865 canon, err := canonicalBug(c, bug) 866 if err != nil { 867 return false, err 868 } 869 return canon.Status == BugStatusOpen, nil 870} 871 872func needRepro(c context.Context, bug *Bug) bool { 873 if !needReproForBug(c, bug) { 874 return false 875 } 876 canon, err := canonicalBug(c, bug) 877 if err != nil { 878 log.Errorf(c, "failed to get canonical bug: %v", err) 879 return false 880 } 881 return needReproForBug(c, canon) 882} 883 884func needReproForBug(c context.Context, bug *Bug) bool { 885 return bug.ReproLevel < ReproLevelC && 886 len(bug.Commits) == 0 && 887 bug.Title != corruptedReportTitle && 888 (bug.NumRepro < maxReproPerBug || 889 bug.ReproLevel == ReproLevelNone && timeSince(c, bug.LastReproTime) > reproRetryPeriod) 890} 891 892func putText(c context.Context, ns, tag string, data []byte, dedup bool) (int64, error) { 893 if ns == "" { 894 return 0, fmt.Errorf("putting text outside of namespace") 895 } 896 if len(data) == 0 { 897 return 0, nil 898 } 899 const ( 900 maxTextLen = 2 << 20 901 maxCompressedLen = 1000 << 10 // datastore entity limit is 1MB 902 ) 903 if len(data) > maxTextLen { 904 data = data[:maxTextLen] 905 } 906 b := new(bytes.Buffer) 907 for { 908 z, _ := gzip.NewWriterLevel(b, gzip.BestCompression) 909 z.Write(data) 910 z.Close() 911 if len(b.Bytes()) < maxCompressedLen { 912 break 913 } 914 data = data[:len(data)/10*9] 915 b.Reset() 916 } 917 var key *datastore.Key 918 if dedup { 919 h := hash.Hash([]byte(ns), b.Bytes()) 920 key = datastore.NewKey(c, tag, "", h.Truncate64(), nil) 921 } else { 922 key = datastore.NewIncompleteKey(c, tag, nil) 923 } 924 text := &Text{ 925 Namespace: ns, 926 Text: b.Bytes(), 927 } 928 key, err := datastore.Put(c, key, text) 929 if err != nil { 930 return 0, err 931 } 932 return key.IntID(), nil 933} 934 935func getText(c context.Context, tag string, id int64) ([]byte, string, error) { 936 if id == 0 { 937 return nil, "", nil 938 } 939 text := new(Text) 940 if err := datastore.Get(c, datastore.NewKey(c, tag, "", id, nil), text); err != nil { 941 return nil, "", fmt.Errorf("failed to read text %v: %v", tag, err) 942 } 943 d, err := gzip.NewReader(bytes.NewBuffer(text.Text)) 944 if err != nil { 945 return nil, "", fmt.Errorf("failed to read text %v: %v", tag, err) 946 } 947 data, err := ioutil.ReadAll(d) 948 if err != nil { 949 return nil, "", fmt.Errorf("failed to read text %v: %v", tag, err) 950 } 951 return data, text.Namespace, nil 952} 953 954// limitLength essentially does return s[:max], 955// but it ensures that we dot not split UTF-8 rune in half. 956// Otherwise appengine python scripts will break badly. 957func limitLength(s string, max int) string { 958 s = strings.TrimSpace(s) 959 if len(s) <= max { 960 return s 961 } 962 for { 963 s = s[:max] 964 r, size := utf8.DecodeLastRuneInString(s) 965 if r != utf8.RuneError || size != 1 { 966 return s 967 } 968 max-- 969 } 970} 971