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 }