github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/manager/covfilter.go (about)

     1  // Copyright 2020 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 manager
     5  
     6  import (
     7  	"bufio"
     8  	"fmt"
     9  	"os"
    10  	"regexp"
    11  	"sort"
    12  	"strconv"
    13  
    14  	"github.com/google/syzkaller/pkg/corpus"
    15  	"github.com/google/syzkaller/pkg/cover/backend"
    16  	"github.com/google/syzkaller/pkg/log"
    17  	"github.com/google/syzkaller/pkg/mgrconfig"
    18  )
    19  
    20  func CoverageFilter(source *ReportGeneratorWrapper, covCfg mgrconfig.CovFilterCfg,
    21  	strict bool) (map[uint64]struct{}, error) {
    22  	if covCfg.Empty() {
    23  		return nil, nil
    24  	}
    25  	rg, err := source.Get()
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  	pcs := make(map[uint64]struct{})
    30  	foreachSymbol := func(apply func(*backend.ObjectUnit)) {
    31  		for _, sym := range rg.Symbols {
    32  			apply(&sym.ObjectUnit)
    33  		}
    34  	}
    35  	if err := covFilterAddFilter(pcs, covCfg.Functions, foreachSymbol, strict); err != nil {
    36  		return nil, err
    37  	}
    38  	foreachUnit := func(apply func(*backend.ObjectUnit)) {
    39  		for _, unit := range rg.Units {
    40  			apply(&unit.ObjectUnit)
    41  		}
    42  	}
    43  	if err := covFilterAddFilter(pcs, covCfg.Files, foreachUnit, strict); err != nil {
    44  		return nil, err
    45  	}
    46  	if err := covFilterAddRawPCs(pcs, covCfg.RawPCs); err != nil {
    47  		return nil, err
    48  	}
    49  	// Note that pcs may include both comparison and block/edge coverage callbacks.
    50  	return pcs, nil
    51  }
    52  
    53  func covFilterAddFilter(pcs map[uint64]struct{}, filters []string, foreach func(func(*backend.ObjectUnit)),
    54  	strict bool) error {
    55  	res, err := compileRegexps(filters)
    56  	if err != nil {
    57  		return err
    58  	}
    59  	used := make(map[*regexp.Regexp][]string)
    60  	foreach(func(unit *backend.ObjectUnit) {
    61  		for _, re := range res {
    62  			if re.MatchString(unit.Name) {
    63  				// We add both coverage points and comparison interception points
    64  				// because executor filters comparisons as well.
    65  				for _, pc := range unit.PCs {
    66  					pcs[pc] = struct{}{}
    67  				}
    68  				for _, pc := range unit.CMPs {
    69  					pcs[pc] = struct{}{}
    70  				}
    71  				used[re] = append(used[re], unit.Name)
    72  				break
    73  			}
    74  		}
    75  	})
    76  	for _, re := range res {
    77  		sort.Strings(used[re])
    78  		log.Logf(0, "coverage filter: %v: %v", re, used[re])
    79  	}
    80  	if strict && len(res) != len(used) {
    81  		return fmt.Errorf("some filters don't match anything")
    82  	}
    83  	return nil
    84  }
    85  
    86  func covFilterAddRawPCs(pcs map[uint64]struct{}, rawPCsFiles []string) error {
    87  	re := regexp.MustCompile(`(0x[0-9a-f]+)(?:: (0x[0-9a-f]+))?`)
    88  	for _, f := range rawPCsFiles {
    89  		rawFile, err := os.Open(f)
    90  		if err != nil {
    91  			return fmt.Errorf("failed to open raw PCs file: %w", err)
    92  		}
    93  		defer rawFile.Close()
    94  		s := bufio.NewScanner(rawFile)
    95  		for s.Scan() {
    96  			match := re.FindStringSubmatch(s.Text())
    97  			if match == nil {
    98  				return fmt.Errorf("bad line: %q", s.Text())
    99  			}
   100  			pc, err := strconv.ParseUint(match[1], 0, 64)
   101  			if err != nil {
   102  				return err
   103  			}
   104  			weight, err := strconv.ParseUint(match[2], 0, 32)
   105  			if match[2] != "" && err != nil {
   106  				return err
   107  			}
   108  			// If no weight is detected, set the weight to 0x1 by default.
   109  			if match[2] == "" || weight < 1 {
   110  				weight = 1
   111  			}
   112  			_ = weight // currently unused
   113  			pcs[pc] = struct{}{}
   114  		}
   115  		if err := s.Err(); err != nil {
   116  			return err
   117  		}
   118  	}
   119  	return nil
   120  }
   121  
   122  func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) {
   123  	var regexps []*regexp.Regexp
   124  	for _, rs := range regexpStrings {
   125  		r, err := regexp.Compile(rs)
   126  		if err != nil {
   127  			return nil, fmt.Errorf("failed to compile regexp: %w", err)
   128  		}
   129  		regexps = append(regexps, r)
   130  	}
   131  	return regexps, nil
   132  }
   133  
   134  type CoverageFilters struct {
   135  	Areas          []corpus.FocusArea
   136  	ExecutorFilter map[uint64]struct{}
   137  }
   138  
   139  func PrepareCoverageFilters(source *ReportGeneratorWrapper, cfg *mgrconfig.Config,
   140  	strict bool) (CoverageFilters, error) {
   141  	var ret CoverageFilters
   142  	needExecutorFilter := len(cfg.Experimental.FocusAreas) > 0
   143  	for _, area := range cfg.Experimental.FocusAreas {
   144  		pcs, err := CoverageFilter(source, area.Filter, strict)
   145  		if err != nil {
   146  			return ret, err
   147  		}
   148  		// KCOV will point to the next instruction, so we need to adjust the map.
   149  		covPCs := make(map[uint64]struct{})
   150  		for pc := range pcs {
   151  			next := backend.NextInstructionPC(cfg.SysTarget, cfg.Type, pc)
   152  			covPCs[next] = struct{}{}
   153  		}
   154  		ret.Areas = append(ret.Areas, corpus.FocusArea{
   155  			Name:     area.Name,
   156  			CoverPCs: covPCs,
   157  			Weight:   area.Weight,
   158  		})
   159  		if area.Filter.Empty() {
   160  			// An empty cover filter indicates that the user is interested in all the coverage.
   161  			needExecutorFilter = false
   162  		}
   163  	}
   164  	if needExecutorFilter {
   165  		ret.ExecutorFilter = map[uint64]struct{}{}
   166  		for _, area := range ret.Areas {
   167  			for pc := range area.CoverPCs {
   168  				ret.ExecutorFilter[pc] = struct{}{}
   169  			}
   170  		}
   171  	}
   172  	return ret, nil
   173  }