github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/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 perName := map[string]*subsystem.Subsystem{} 145 for _, entry := range list { 146 entry.Syscalls = ctx.extraRules.subsystemCalls[entry.Name] 147 _, entry.NoReminders = ctx.extraRules.noReminders[entry.Name] 148 _, entry.NoIndirectCc = ctx.extraRules.noIndirectCc[entry.Name] 149 perName[entry.Name] = entry 150 } 151 for from, toNames := range ctx.extraRules.addParents { 152 item := perName[from] 153 if item == nil { 154 return fmt.Errorf("unknown subsystem: %q", from) 155 } 156 exists := map[string]bool{} 157 for _, p := range item.Parents { 158 exists[p.Name] = true 159 } 160 for _, toName := range toNames { 161 if exists[toName] { 162 continue 163 } 164 if perName[toName] == nil { 165 return fmt.Errorf("unknown parent subsystem: %q", toName) 166 } 167 item.Parents = append(item.Parents, perName[toName]) 168 } 169 } 170 transitiveReduction(list) 171 return nil 172 } 173 174 func mergeRawRecords(records []*maintainersRecord, email string) *subsystem.Subsystem { 175 var lists []string 176 subsystem := &subsystem.Subsystem{} 177 for _, record := range records { 178 rule := record.ToPathRule() 179 if !rule.IsEmpty() { 180 subsystem.PathRules = append(subsystem.PathRules, rule) 181 } 182 lists = append(lists, record.lists...) 183 } 184 if email != "" { 185 subsystem.Lists = []string{email} 186 } else if len(lists) > 0 { 187 subsystem.Lists = unique(lists) 188 } 189 subsystem.Maintainers = maintainersFromRecords(records) 190 return subsystem 191 } 192 193 func unique(list []string) []string { 194 m := make(map[string]struct{}) 195 for _, s := range list { 196 m[s] = struct{}{} 197 } 198 ret := []string{} 199 for s := range m { 200 ret = append(ret, s) 201 } 202 sort.Strings(ret) 203 return ret 204 } 205 206 func maintainersFromRecords(records []*maintainersRecord) []string { 207 // Generally we avoid merging maintainers from too many MAINTAINERS records, 208 // as we may end up pinging too many unrelated people. 209 // But in some cases we can still reliably collect the information. 210 if len(records) <= 1 { 211 // First of all, we're fine if there was just on record. 212 return unique(records[0].maintainers) 213 } 214 // Also let's take a look at the entries that have tree information. 215 // They seem to be present only in the most important entries. 216 perTrees := map[string][][]string{} 217 for _, record := range records { 218 if len(record.trees) == 0 { 219 continue 220 } 221 sort.Strings(record.trees) 222 key := fmt.Sprintf("%v", record.trees) 223 perTrees[key] = append(perTrees[key], record.maintainers) 224 } 225 if len(perTrees) > 1 { 226 // There are several sets of trees, no way to determine the most important. 227 return nil 228 } 229 var maintainerLists [][]string 230 for _, value := range perTrees { 231 maintainerLists = value 232 } 233 // Now let's take the intersection of lists. 234 counts := map[string]int{} 235 var retList []string 236 for _, list := range maintainerLists { 237 list = unique(list) 238 for _, email := range list { 239 counts[email]++ 240 if counts[email] == len(maintainerLists) { 241 retList = append(retList, email) 242 } 243 } 244 } 245 return retList 246 } 247 248 func getMaintainers(root fs.FS) ([]*maintainersRecord, error) { 249 f, err := root.Open("MAINTAINERS") 250 if err != nil { 251 return nil, fmt.Errorf("failed to open the MAINTAINERS file: %w", err) 252 } 253 defer f.Close() 254 return parseLinuxMaintainers(f) 255 }