github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/dashboard/app/subsystem.go (about) 1 // Copyright 2023 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 "context" 8 "fmt" 9 "reflect" 10 "sort" 11 "time" 12 13 "github.com/google/syzkaller/pkg/debugtracer" 14 "github.com/google/syzkaller/pkg/subsystem" 15 db "google.golang.org/appengine/v2/datastore" 16 "google.golang.org/appengine/v2/log" 17 ) 18 19 // reassignBugSubsystems is expected to be periodically called to refresh old automatic 20 // subsystem assignments. 21 func reassignBugSubsystems(c context.Context, ns string, count int) error { 22 service := getNsConfig(c, ns).Subsystems.Service 23 if service == nil { 24 return nil 25 } 26 bugs, keys, err := bugsToUpdateSubsystems(c, ns, count) 27 if err != nil { 28 return err 29 } 30 log.Infof(c, "updating subsystems for %d bugs in %#v", len(keys), ns) 31 rev := getNsConfig(c, ns).Subsystems.Revision 32 for i, bugKey := range keys { 33 if bugs[i].hasUserSubsystems() { 34 // It might be that the user-set subsystem no longer exists. 35 // For now let's just log an error in this case. 36 checkOutdatedSubsystems(c, service, bugs[i]) 37 // If we don't set the latst revision, we'll have to update this 38 // bug over and over again. 39 err = updateBugSubsystems(c, bugKey, nil, updateRevision(rev)) 40 } else { 41 var list []*subsystem.Subsystem 42 list, err = inferSubsystems(c, bugs[i], bugKey, &debugtracer.NullTracer{}) 43 if err != nil { 44 return fmt.Errorf("failed to infer subsystems: %w", err) 45 } 46 err = updateBugSubsystems(c, bugKey, list, autoInference(rev)) 47 } 48 if err != nil { 49 return fmt.Errorf("failed to save subsystems: %w", err) 50 } 51 } 52 return nil 53 } 54 55 func bugsToUpdateSubsystems(c context.Context, ns string, count int) ([]*Bug, []*db.Key, error) { 56 now := timeNow(c) 57 rev := getSubsystemRevision(c, ns) 58 queries := []*db.Query{ 59 // If revision has been updated, first update open bugs. 60 db.NewQuery("Bug"). 61 Filter("Namespace=", ns). 62 Filter("Status=", BugStatusOpen). 63 Filter("SubsystemsRev<", rev), 64 // The next priority is the regular update of open bugs. 65 db.NewQuery("Bug"). 66 Filter("Namespace=", ns). 67 Filter("Status=", BugStatusOpen). 68 Filter("SubsystemsTime<", now.Add(-openBugsUpdateTime)), 69 // Then let's consider the update of closed bugs. 70 db.NewQuery("Bug"). 71 Filter("Namespace=", ns). 72 Filter("Status=", BugStatusFixed). 73 Filter("SubsystemsRev<", rev), 74 // And, at the end, everything else. 75 db.NewQuery("Bug"). 76 Filter("Namespace=", ns). 77 Filter("SubsystemsRev<", rev), 78 } 79 var bugs []*Bug 80 var keys []*db.Key 81 for i, query := range queries { 82 if count <= 0 { 83 break 84 } 85 var tmpBugs []*Bug 86 tmpKeys, err := query.Limit(count).GetAll(c, &tmpBugs) 87 if err != nil { 88 return nil, nil, fmt.Errorf("query %d failed: %w", i, err) 89 } 90 bugs = append(bugs, tmpBugs...) 91 keys = append(keys, tmpKeys...) 92 count -= len(tmpKeys) 93 } 94 return bugs, keys, nil 95 } 96 97 func checkOutdatedSubsystems(c context.Context, service *subsystem.Service, bug *Bug) { 98 for _, item := range bug.LabelValues(SubsystemLabel) { 99 if service.ByName(item.Value) == nil { 100 log.Errorf(c, "ns=%s bug=%s subsystem %s no longer exists", bug.Namespace, bug.Title, item.Value) 101 } 102 } 103 } 104 105 type ( 106 autoInference int 107 updateRevision int 108 ) 109 110 func updateBugSubsystems(c context.Context, bugKey *db.Key, 111 list []*subsystem.Subsystem, info interface{}) error { 112 now := timeNow(c) 113 return updateSingleBug(c, bugKey, func(bug *Bug) error { 114 switch v := info.(type) { 115 case autoInference: 116 logSubsystemChange(c, bug, list) 117 bug.SetAutoSubsystems(c, list, now, int(v)) 118 case updateRevision: 119 bug.SubsystemsRev = int(v) 120 bug.SubsystemsTime = now 121 } 122 return nil 123 }) 124 } 125 126 func logSubsystemChange(c context.Context, bug *Bug, new []*subsystem.Subsystem) { 127 var oldNames, newNames []string 128 for _, item := range bug.LabelValues(SubsystemLabel) { 129 oldNames = append(oldNames, item.Value) 130 } 131 for _, item := range new { 132 newNames = append(newNames, item.Name) 133 } 134 sort.Strings(oldNames) 135 sort.Strings(newNames) 136 if !reflect.DeepEqual(oldNames, newNames) { 137 log.Infof(c, "bug %s: subsystems set from %v to %v", 138 bug.keyHash(c), oldNames, newNames) 139 } 140 } 141 142 const ( 143 // We load the top crashesForInference crashes to determine the bug subsystem(s). 144 crashesForInference = 7 145 // How often we update open bugs. 146 openBugsUpdateTime = time.Hour * 24 * 30 147 ) 148 149 // inferSubsystems determines the best yet possible estimate of the bug's subsystems. 150 func inferSubsystems(c context.Context, bug *Bug, bugKey *db.Key, 151 tracer debugtracer.DebugTracer) ([]*subsystem.Subsystem, error) { 152 service := getSubsystemService(c, bug.Namespace) 153 if service == nil { 154 // There's nothing we can do. 155 return nil, nil 156 } 157 dbCrashes, dbCrashKeys, err := queryCrashesForBug(c, bugKey, crashesForInference) 158 if err != nil { 159 return nil, err 160 } 161 crashes := []*subsystem.Crash{} 162 for i, dbCrash := range dbCrashes { 163 crash := &subsystem.Crash{} 164 if len(dbCrash.ReportElements.GuiltyFiles) > 0 { 165 // For now we anyway only store one. 166 crash.GuiltyPath = dbCrash.ReportElements.GuiltyFiles[0] 167 } 168 if dbCrash.ReproSyz != 0 { 169 crash.SyzRepro, _, err = getText(c, textReproSyz, dbCrash.ReproSyz) 170 if err != nil { 171 return nil, fmt.Errorf("failed to load syz repro for %s: %w", 172 dbCrashKeys[i], err) 173 } 174 } 175 crashes = append(crashes, crash) 176 } 177 return service.TracedExtract(crashes, tracer), nil 178 } 179 180 // subsystemMaintainers queries the list of emails to send the bug to. 181 func subsystemMaintainers(c context.Context, ns, subsystemName string) []string { 182 service := getNsConfig(c, ns).Subsystems.Service 183 if service == nil { 184 return nil 185 } 186 item := service.ByName(subsystemName) 187 if item == nil { 188 return nil 189 } 190 return item.Emails() 191 } 192 193 func getSubsystemService(c context.Context, ns string) *subsystem.Service { 194 return getNsConfig(c, ns).Subsystems.Service 195 } 196 197 func getSubsystemRevision(c context.Context, ns string) int { 198 return getNsConfig(c, ns).Subsystems.Revision 199 } 200 201 func subsystemListURL(c context.Context, ns string) string { 202 if getNsConfig(c, ns).Subsystems.Service == nil { 203 return "" 204 } 205 return fmt.Sprintf("%v/%v/subsystems?all=true", appURL(c), ns) 206 }