github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/subsystem/linux/subsystems.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 linux 5 6 import ( 7 "fmt" 8 "io/fs" 9 "os" 10 "regexp" 11 "sort" 12 13 "github.com/google/syzkaller/pkg/subsystem" 14 ) 15 16 func ListFromRepo(repo string) ([]*subsystem.Subsystem, error) { 17 return listFromRepoInner(os.DirFS(repo), linuxSubsystemRules) 18 } 19 20 // listFromRepoInner allows for better testing. 21 func listFromRepoInner(root fs.FS, rules *customRules) ([]*subsystem.Subsystem, error) { 22 records, err := getMaintainers(root) 23 if err != nil { 24 return nil, err 25 } 26 removeMatchingPatterns(records, dropPatterns) 27 ctx := &linuxCtx{ 28 root: root, 29 rawRecords: records, 30 extraRules: rules, 31 } 32 extraList, err := ctx.groupByRules() 33 if err != nil { 34 return nil, err 35 } 36 list := append(ctx.groupByList(), extraList...) 37 matrix, err := BuildCoincidenceMatrix(root, list, dropPatterns) 38 if err != nil { 39 return nil, err 40 } 41 list, err = parentTransformations(matrix, list) 42 if err != nil { 43 return nil, err 44 } 45 if err := setSubsystemNames(list); err != nil { 46 return nil, fmt.Errorf("failed to set names: %w", err) 47 } 48 if err := ctx.applyExtraRules(list); err != nil { 49 return nil, fmt.Errorf("failed to apply extra rules: %w", err) 50 } 51 52 // Sort subsystems by name to keep output consistent. 53 sort.Slice(list, func(i, j int) bool { return list[i].Name < list[j].Name }) 54 // Sort path rules to keep output consistent. 55 for _, entity := range list { 56 sort.Slice(entity.PathRules, func(i, j int) bool { 57 a, b := entity.PathRules[i], entity.PathRules[j] 58 if a.IncludeRegexp != b.IncludeRegexp { 59 return a.IncludeRegexp < b.IncludeRegexp 60 } 61 return a.ExcludeRegexp < b.ExcludeRegexp 62 }) 63 } 64 return list, nil 65 } 66 67 type linuxCtx struct { 68 root fs.FS 69 rawRecords []*maintainersRecord 70 extraRules *customRules 71 } 72 73 var ( 74 // Some of the patterns are not really needed for bug subsystem inteference and 75 // only complicate the manual review of the rules. 76 dropPatterns = regexp.MustCompile(`^(Documentation|scripts|samples|tools)|Makefile`) 77 ) 78 79 func (ctx *linuxCtx) groupByList() []*subsystem.Subsystem { 80 perList := make(map[string][]*maintainersRecord) 81 for _, record := range ctx.rawRecords { 82 for _, list := range record.lists { 83 perList[list] = append(perList[list], record) 84 } 85 } 86 var exclude map[string]struct{} 87 if ctx.extraRules != nil { 88 exclude = ctx.extraRules.notSubsystemEmails 89 } 90 ret := []*subsystem.Subsystem{} 91 for email, list := range perList { 92 if _, skip := exclude[email]; skip { 93 continue 94 } 95 s := mergeRawRecords(list, email) 96 // Skip empty subsystems. 97 if len(s.PathRules) > 0 { 98 ret = append(ret, s) 99 } 100 } 101 return ret 102 } 103 104 func (ctx *linuxCtx) groupByRules() ([]*subsystem.Subsystem, error) { 105 if ctx.extraRules == nil { 106 return nil, nil 107 } 108 perName := map[string]*maintainersRecord{} 109 for _, item := range ctx.rawRecords { 110 perName[item.name] = item 111 } 112 var ret []*subsystem.Subsystem 113 exclude := map[*maintainersRecord]struct{}{} 114 for name, recordNames := range ctx.extraRules.extraSubsystems { 115 matching := []*maintainersRecord{} 116 for _, recordName := range recordNames { 117 record := perName[recordName] 118 if record == nil { 119 return nil, fmt.Errorf("MAINTAINERS record not found: %#v", recordName) 120 } 121 exclude[record] = struct{}{} 122 matching = append(matching, record) 123 } 124 s := mergeRawRecords(matching, "") 125 s.Name = name 126 ret = append(ret, s) 127 } 128 // Exclude rawRecords from further consideration. 129 var newRecords []*maintainersRecord 130 for _, record := range ctx.rawRecords { 131 if _, ok := exclude[record]; ok { 132 continue 133 } 134 newRecords = append(newRecords, record) 135 } 136 ctx.rawRecords = newRecords 137 return ret, nil 138 } 139 140 func (ctx *linuxCtx) applyExtraRules(list []*subsystem.Subsystem) error { 141 if ctx.extraRules == nil { 142 return nil 143 } 144 if err := noDanglingRules(list, ctx.extraRules.subsystemCalls); err != nil { 145 return fmt.Errorf("subsystemCalls rules check failed: %w", err) 146 } 147 if err := noDanglingRules(list, ctx.extraRules.noReminders); err != nil { 148 return fmt.Errorf("noReminders rules check failed: %w", err) 149 } 150 if err := noDanglingRules(list, ctx.extraRules.noIndirectCc); err != nil { 151 return fmt.Errorf("noIndirectCc rules check failed: %w", err) 152 } 153 perName := map[string]*subsystem.Subsystem{} 154 for _, entry := range list { 155 entry.Syscalls = ctx.extraRules.subsystemCalls[entry.Name] 156 _, entry.NoReminders = ctx.extraRules.noReminders[entry.Name] 157 _, entry.NoIndirectCc = ctx.extraRules.noIndirectCc[entry.Name] 158 perName[entry.Name] = entry 159 } 160 for from, toNames := range ctx.extraRules.addParents { 161 item := perName[from] 162 if item == nil { 163 return fmt.Errorf("unknown subsystem: %q", from) 164 } 165 exists := map[string]bool{} 166 for _, p := range item.Parents { 167 exists[p.Name] = true 168 } 169 for _, toName := range toNames { 170 if exists[toName] { 171 continue 172 } 173 if perName[toName] == nil { 174 return fmt.Errorf("unknown parent subsystem: %q", toName) 175 } 176 item.Parents = append(item.Parents, perName[toName]) 177 } 178 } 179 transitiveReduction(list) 180 return nil 181 } 182 183 // Check that there are no rules that don't refer to any subsystem from the list. 184 func noDanglingRules[T any](list []*subsystem.Subsystem, rules map[string]T) error { 185 usedRule := map[string]struct{}{} 186 for _, entry := range list { 187 if _, ok := rules[entry.Name]; !ok { 188 continue 189 } 190 usedRule[entry.Name] = struct{}{} 191 } 192 if len(usedRule) == len(rules) { 193 return nil 194 } 195 var dangling []string 196 for key := range rules { 197 if _, ok := usedRule[key]; ok { 198 continue 199 } 200 dangling = append(dangling, key) 201 } 202 return fmt.Errorf("unused keys: %q", dangling) 203 } 204 205 func mergeRawRecords(records []*maintainersRecord, email string) *subsystem.Subsystem { 206 var lists []string 207 subsystem := &subsystem.Subsystem{} 208 for _, record := range records { 209 rule := record.ToPathRule() 210 if !rule.IsEmpty() { 211 subsystem.PathRules = append(subsystem.PathRules, rule) 212 } 213 lists = append(lists, record.lists...) 214 } 215 if email != "" { 216 subsystem.Lists = []string{email} 217 } else if len(lists) > 0 { 218 subsystem.Lists = unique(lists) 219 } 220 subsystem.Maintainers = maintainersFromRecords(records) 221 return subsystem 222 } 223 224 func unique(list []string) []string { 225 m := make(map[string]struct{}) 226 for _, s := range list { 227 m[s] = struct{}{} 228 } 229 ret := []string{} 230 for s := range m { 231 ret = append(ret, s) 232 } 233 sort.Strings(ret) 234 return ret 235 } 236 237 func maintainersFromRecords(records []*maintainersRecord) []string { 238 // Generally we avoid merging maintainers from too many MAINTAINERS records, 239 // as we may end up pinging too many unrelated people. 240 // But in some cases we can still reliably collect the information. 241 if len(records) <= 1 { 242 // First of all, we're fine if there was just on record. 243 return unique(records[0].maintainers) 244 } 245 // Also let's take a look at the entries that have tree information. 246 // They seem to be present only in the most important entries. 247 perTrees := map[string][][]string{} 248 for _, record := range records { 249 if len(record.trees) == 0 { 250 continue 251 } 252 sort.Strings(record.trees) 253 key := fmt.Sprintf("%v", record.trees) 254 perTrees[key] = append(perTrees[key], record.maintainers) 255 } 256 if len(perTrees) > 1 { 257 // There are several sets of trees, no way to determine the most important. 258 return nil 259 } 260 var maintainerLists [][]string 261 for _, value := range perTrees { 262 maintainerLists = value 263 } 264 // Now let's take the intersection of lists. 265 counts := map[string]int{} 266 var retList []string 267 for _, list := range maintainerLists { 268 list = unique(list) 269 for _, email := range list { 270 counts[email]++ 271 if counts[email] == len(maintainerLists) { 272 retList = append(retList, email) 273 } 274 } 275 } 276 return retList 277 } 278 279 func getMaintainers(root fs.FS) ([]*maintainersRecord, error) { 280 f, err := root.Open("MAINTAINERS") 281 if err != nil { 282 return nil, fmt.Errorf("failed to open the MAINTAINERS file: %w", err) 283 } 284 defer f.Close() 285 return parseLinuxMaintainers(f) 286 }