diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2024-09-04 17:24:25 +0200 |
|---|---|---|
| committer | Aleksandr Nogikh <nogikh@google.com> | 2024-09-06 12:51:13 +0000 |
| commit | a72ab0ee0af4b1d0d401c09436e1e9ff04bc46e6 (patch) | |
| tree | 0177e2de1f90116dd2328a7b5867ca945060731f /pkg | |
| parent | ece8ad6a2850d003b371a4b4e16c7d5647720992 (diff) | |
pkg/manager: move coverage filter code out of syz-manager
This will enable the reuse of the functionality elsewhere.
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/manager/covfilter.go | 140 | ||||
| -rw-r--r-- | pkg/manager/report_generator.go | 70 | ||||
| -rw-r--r-- | pkg/mgrconfig/config.go | 4 | ||||
| -rw-r--r-- | pkg/mgrconfig/load.go | 4 |
4 files changed, 214 insertions, 4 deletions
diff --git a/pkg/manager/covfilter.go b/pkg/manager/covfilter.go new file mode 100644 index 000000000..420eb40b6 --- /dev/null +++ b/pkg/manager/covfilter.go @@ -0,0 +1,140 @@ +// Copyright 2020 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package manager + +import ( + "bufio" + "fmt" + "os" + "regexp" + "sort" + "strconv" + + "github.com/google/syzkaller/pkg/cover/backend" + "github.com/google/syzkaller/pkg/log" + "github.com/google/syzkaller/pkg/mgrconfig" +) + +func CreateCoverageFilter(source *ReportGeneratorWrapper, covCfg mgrconfig.CovFilterCfg) ([]uint64, + map[uint64]struct{}, error) { + if covCfg.Empty() { + return nil, nil, nil + } + rg, err := source.Get() + if err != nil { + return nil, nil, err + } + pcs := make(map[uint64]struct{}) + foreachSymbol := func(apply func(*backend.ObjectUnit)) { + for _, sym := range rg.Symbols { + apply(&sym.ObjectUnit) + } + } + if err := covFilterAddFilter(pcs, covCfg.Functions, foreachSymbol); err != nil { + return nil, nil, err + } + foreachUnit := func(apply func(*backend.ObjectUnit)) { + for _, unit := range rg.Units { + apply(&unit.ObjectUnit) + } + } + if err := covFilterAddFilter(pcs, covCfg.Files, foreachUnit); err != nil { + return nil, nil, err + } + if err := covFilterAddRawPCs(pcs, covCfg.RawPCs); err != nil { + return nil, nil, err + } + // Copy pcs into execPCs. This is used to filter coverage in the executor. + execPCs := make([]uint64, 0, len(pcs)) + for pc := range pcs { + execPCs = append(execPCs, pc) + } + // PCs from CMPs are deleted to calculate `filtered coverage` statistics. + for _, sym := range rg.Symbols { + for _, pc := range sym.CMPs { + delete(pcs, pc) + } + } + return execPCs, pcs, nil +} + +func covFilterAddFilter(pcs map[uint64]struct{}, filters []string, foreach func(func(*backend.ObjectUnit))) error { + res, err := compileRegexps(filters) + if err != nil { + return err + } + used := make(map[*regexp.Regexp][]string) + foreach(func(unit *backend.ObjectUnit) { + for _, re := range res { + if re.MatchString(unit.Name) { + // We add both coverage points and comparison interception points + // because executor filters comparisons as well. + for _, pc := range unit.PCs { + pcs[pc] = struct{}{} + } + for _, pc := range unit.CMPs { + pcs[pc] = struct{}{} + } + used[re] = append(used[re], unit.Name) + break + } + } + }) + for _, re := range res { + sort.Strings(used[re]) + log.Logf(0, "coverage filter: %v: %v", re, used[re]) + } + if len(res) != len(used) { + return fmt.Errorf("some filters don't match anything") + } + return nil +} + +func covFilterAddRawPCs(pcs map[uint64]struct{}, rawPCsFiles []string) error { + re := regexp.MustCompile(`(0x[0-9a-f]+)(?:: (0x[0-9a-f]+))?`) + for _, f := range rawPCsFiles { + rawFile, err := os.Open(f) + if err != nil { + return fmt.Errorf("failed to open raw PCs file: %w", err) + } + defer rawFile.Close() + s := bufio.NewScanner(rawFile) + for s.Scan() { + match := re.FindStringSubmatch(s.Text()) + if match == nil { + return fmt.Errorf("bad line: %q", s.Text()) + } + pc, err := strconv.ParseUint(match[1], 0, 64) + if err != nil { + return err + } + weight, err := strconv.ParseUint(match[2], 0, 32) + if match[2] != "" && err != nil { + return err + } + // If no weight is detected, set the weight to 0x1 by default. + if match[2] == "" || weight < 1 { + weight = 1 + } + _ = weight // currently unused + pcs[pc] = struct{}{} + } + if err := s.Err(); err != nil { + return err + } + } + return nil +} + +func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) { + var regexps []*regexp.Regexp + for _, rs := range regexpStrings { + r, err := regexp.Compile(rs) + if err != nil { + return nil, fmt.Errorf("failed to compile regexp: %w", err) + } + regexps = append(regexps, r) + } + return regexps, nil +} diff --git a/pkg/manager/report_generator.go b/pkg/manager/report_generator.go new file mode 100644 index 000000000..b3d293ef1 --- /dev/null +++ b/pkg/manager/report_generator.go @@ -0,0 +1,70 @@ +// Copyright 2015 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package manager + +import ( + "fmt" + "sync" + + "github.com/google/syzkaller/pkg/cover" + "github.com/google/syzkaller/pkg/cover/backend" + "github.com/google/syzkaller/pkg/log" + "github.com/google/syzkaller/pkg/mgrconfig" + "github.com/google/syzkaller/pkg/vminfo" +) + +type ReportGeneratorWrapper struct { + cfg *mgrconfig.Config + modules []*vminfo.KernelModule + + mu sync.Mutex + initialized bool + cached *cover.ReportGenerator +} + +func ReportGeneratorCache(cfg *mgrconfig.Config) *ReportGeneratorWrapper { + return &ReportGeneratorWrapper{cfg: cfg} +} + +func (w *ReportGeneratorWrapper) Get() (*cover.ReportGenerator, error) { + w.mu.Lock() + defer w.mu.Unlock() + if !w.initialized { + return nil, fmt.Errorf("report generator creation before Init() is called") + } + if w.cached == nil { + log.Logf(0, "initializing coverage information...") + rg, err := cover.MakeReportGenerator(w.cfg, w.cfg.KernelSubsystem, w.modules, w.cfg.RawCover) + if err != nil { + return nil, err + } + w.cached = rg + } + return w.cached, nil +} + +func (w *ReportGeneratorWrapper) Init(modules []*vminfo.KernelModule) { + w.mu.Lock() + defer w.mu.Unlock() + if w.initialized { + panic("Init() called twice") + } + w.initialized = true + w.modules = modules +} + +func (w *ReportGeneratorWrapper) Reset() { + w.mu.Lock() + defer w.mu.Unlock() + w.cached = nil +} + +func CoverToPCs(cfg *mgrconfig.Config, cov []uint64) []uint64 { + pcs := make([]uint64, 0, len(cov)) + for _, pc := range cov { + prev := backend.PreviousInstructionPC(cfg.SysTarget, cfg.Type, pc) + pcs = append(pcs, prev) + } + return pcs +} diff --git a/pkg/mgrconfig/config.go b/pkg/mgrconfig/config.go index 2ea0d8ad9..f4857e613 100644 --- a/pkg/mgrconfig/config.go +++ b/pkg/mgrconfig/config.go @@ -152,7 +152,7 @@ type Config struct { // "pcs": specify raw PC table files name. // Each line of the file should be: "64-bit-pc:32-bit-weight\n". // eg. "0xffffffff81000000:0x10\n" - CovFilter covFilterCfg `json:"cover_filter,omitempty"` + CovFilter CovFilterCfg `json:"cover_filter,omitempty"` // For each prog in the corpus, remember the raw array of PCs obtained from the kernel. // It can be useful for debugging syzkaller descriptions and syzkaller itself. @@ -242,7 +242,7 @@ type Subsystem struct { Paths []string `json:"path"` } -type covFilterCfg struct { +type CovFilterCfg struct { Files []string `json:"files,omitempty"` Functions []string `json:"functions,omitempty"` RawPCs []string `json:"pcs,omitempty"` diff --git a/pkg/mgrconfig/load.go b/pkg/mgrconfig/load.go index aa22b3ea8..76a0bcf14 100644 --- a/pkg/mgrconfig/load.go +++ b/pkg/mgrconfig/load.go @@ -260,8 +260,8 @@ func checkNonEmpty(fields ...string) error { return nil } -func (cfg *Config) HasCovFilter() bool { - return len(cfg.CovFilter.Functions)+len(cfg.CovFilter.Files)+len(cfg.CovFilter.RawPCs) != 0 +func (cov *CovFilterCfg) Empty() bool { + return len(cov.Functions)+len(cov.Files)+len(cov.RawPCs) == 0 } func (cfg *Config) CompleteKernelDirs() { |
