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  }