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  }