github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/syz-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 main
     5  
     6  import (
     7  	"bufio"
     8  	"encoding/binary"
     9  	"fmt"
    10  	"os"
    11  	"regexp"
    12  	"sort"
    13  	"strconv"
    14  
    15  	"github.com/google/syzkaller/pkg/cover"
    16  	"github.com/google/syzkaller/pkg/cover/backend"
    17  	"github.com/google/syzkaller/pkg/log"
    18  	"github.com/google/syzkaller/pkg/mgrconfig"
    19  	"github.com/google/syzkaller/sys/targets"
    20  )
    21  
    22  func createCoverageFilter(cfg *mgrconfig.Config, modules []cover.KernelModule) (
    23  	map[uint32]uint32, map[uint32]uint32, error) {
    24  	if !cfg.HasCovFilter() {
    25  		return nil, nil, nil
    26  	}
    27  	// Always initialize ReportGenerator because RPCServer.NewInput will need it to filter coverage.
    28  	rg, err := getReportGenerator(cfg, modules)
    29  	if err != nil {
    30  		return nil, nil, err
    31  	}
    32  	pcs := make(map[uint32]uint32)
    33  	foreachSymbol := func(apply func(*backend.ObjectUnit)) {
    34  		for _, sym := range rg.Symbols {
    35  			apply(&sym.ObjectUnit)
    36  		}
    37  	}
    38  	if err := covFilterAddFilter(pcs, cfg.CovFilter.Functions, foreachSymbol); err != nil {
    39  		return nil, nil, err
    40  	}
    41  	foreachUnit := func(apply func(*backend.ObjectUnit)) {
    42  		for _, unit := range rg.Units {
    43  			apply(&unit.ObjectUnit)
    44  		}
    45  	}
    46  	if err := covFilterAddFilter(pcs, cfg.CovFilter.Files, foreachUnit); err != nil {
    47  		return nil, nil, err
    48  	}
    49  	if err := covFilterAddRawPCs(pcs, cfg.CovFilter.RawPCs); err != nil {
    50  		return nil, nil, err
    51  	}
    52  	if len(pcs) == 0 {
    53  		return nil, nil, nil
    54  	}
    55  	if !cfg.SysTarget.ExecutorUsesShmem {
    56  		return nil, nil, fmt.Errorf("coverage filter is only supported for targets that use shmem")
    57  	}
    58  	// Copy pcs into execPCs. This is used to filter coverage in the executor.
    59  	execPCs := make(map[uint32]uint32)
    60  	for pc, val := range pcs {
    61  		execPCs[pc] = val
    62  	}
    63  	// PCs from CMPs are deleted to calculate `filtered coverage` statistics
    64  	// in syz-manager.
    65  	for _, sym := range rg.Symbols {
    66  		for _, pc := range sym.CMPs {
    67  			delete(pcs, uint32(pc))
    68  		}
    69  	}
    70  	return execPCs, pcs, nil
    71  }
    72  
    73  func covFilterAddFilter(pcs map[uint32]uint32, filters []string, foreach func(func(*backend.ObjectUnit))) error {
    74  	res, err := compileRegexps(filters)
    75  	if err != nil {
    76  		return err
    77  	}
    78  	used := make(map[*regexp.Regexp][]string)
    79  	foreach(func(unit *backend.ObjectUnit) {
    80  		for _, re := range res {
    81  			if re.MatchString(unit.Name) {
    82  				// We add both coverage points and comparison interception points
    83  				// because executor filters comparisons as well.
    84  				for _, pc := range unit.PCs {
    85  					pcs[uint32(pc)] = 1
    86  				}
    87  				for _, pc := range unit.CMPs {
    88  					pcs[uint32(pc)] = 1
    89  				}
    90  				used[re] = append(used[re], unit.Name)
    91  				break
    92  			}
    93  		}
    94  	})
    95  	for _, re := range res {
    96  		sort.Strings(used[re])
    97  		log.Logf(0, "coverage filter: %v: %v", re, used[re])
    98  	}
    99  	if len(res) != len(used) {
   100  		return fmt.Errorf("some filters don't match anything")
   101  	}
   102  	return nil
   103  }
   104  
   105  func covFilterAddRawPCs(pcs map[uint32]uint32, rawPCsFiles []string) error {
   106  	re := regexp.MustCompile(`(0x[0-9a-f]+)(?:: (0x[0-9a-f]+))?`)
   107  	for _, f := range rawPCsFiles {
   108  		rawFile, err := os.Open(f)
   109  		if err != nil {
   110  			return fmt.Errorf("failed to open raw PCs file: %w", err)
   111  		}
   112  		defer rawFile.Close()
   113  		s := bufio.NewScanner(rawFile)
   114  		for s.Scan() {
   115  			match := re.FindStringSubmatch(s.Text())
   116  			if match == nil {
   117  				return fmt.Errorf("bad line: %q", s.Text())
   118  			}
   119  			pc, err := strconv.ParseUint(match[1], 0, 64)
   120  			if err != nil {
   121  				return err
   122  			}
   123  			weight, err := strconv.ParseUint(match[2], 0, 32)
   124  			if match[2] != "" && err != nil {
   125  				return err
   126  			}
   127  			// If no weight is detected, set the weight to 0x1 by default.
   128  			if match[2] == "" || weight < 1 {
   129  				weight = 1
   130  			}
   131  			pcs[uint32(pc)] = uint32(weight)
   132  		}
   133  		if err := s.Err(); err != nil {
   134  			return err
   135  		}
   136  	}
   137  	return nil
   138  }
   139  
   140  func createCoverageBitmap(target *targets.Target, pcs map[uint32]uint32) []byte {
   141  	// Return nil if filtering is not used.
   142  	if len(pcs) == 0 {
   143  		return nil
   144  	}
   145  	start, size := coverageFilterRegion(pcs)
   146  	log.Logf(0, "coverage filter from 0x%x to 0x%x, size 0x%x, pcs %v", start, start+size, size, len(pcs))
   147  	// The file starts with two uint32: covFilterStart and covFilterSize,
   148  	// and a bitmap with size ((covFilterSize>>4)/8+2 bytes follow them.
   149  	// 8-bit = 1-byte
   150  	data := make([]byte, 8+((size>>4)/8+2))
   151  	order := binary.ByteOrder(binary.BigEndian)
   152  	if target.LittleEndian {
   153  		order = binary.LittleEndian
   154  	}
   155  	order.PutUint32(data, start)
   156  	order.PutUint32(data[4:], size)
   157  
   158  	bitmap := data[8:]
   159  	for pc := range pcs {
   160  		// The lowest 4-bit is dropped.
   161  		pc = uint32(backend.NextInstructionPC(target, uint64(pc)))
   162  		pc = (pc - start) >> 4
   163  		bitmap[pc/8] |= (1 << (pc % 8))
   164  	}
   165  	return data
   166  }
   167  
   168  func coverageFilterRegion(pcs map[uint32]uint32) (uint32, uint32) {
   169  	start, end := ^uint32(0), uint32(0)
   170  	for pc := range pcs {
   171  		if start > pc {
   172  			start = pc
   173  		}
   174  		if end < pc {
   175  			end = pc
   176  		}
   177  	}
   178  	return start, end - start
   179  }
   180  
   181  func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) {
   182  	var regexps []*regexp.Regexp
   183  	for _, rs := range regexpStrings {
   184  		r, err := regexp.Compile(rs)
   185  		if err != nil {
   186  			return nil, fmt.Errorf("failed to compile regexp: %w", err)
   187  		}
   188  		regexps = append(regexps, r)
   189  	}
   190  	return regexps, nil
   191  }