aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/subsystem
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2023-01-18 15:39:35 +0100
committerAleksandr Nogikh <wp32pw@gmail.com>2023-02-10 14:34:44 +0100
commit7ab1cfa1ba21428ff54bc4d6890628074326e339 (patch)
tree94bf92a4dffc5e5d5a10e6fc6ec67e5b1eaff0fc /pkg/subsystem
parentb1bb0c3d3f03d5f6ab34f8dfac388f63c2011e0d (diff)
pkg/subsystem/match: add coincidence matrix
We'll need it for determining overlapping subsystems and for the inferece of parent-child relationships. Add the code to calculate a coincidence matrix from a filesystem.
Diffstat (limited to 'pkg/subsystem')
-rw-r--r--pkg/subsystem/match/coincidence.go55
-rw-r--r--pkg/subsystem/match/coincidence_test.go40
-rw-r--r--pkg/subsystem/match/path_coincidence.go68
-rw-r--r--pkg/subsystem/match/path_coincidence_test.go49
4 files changed, 212 insertions, 0 deletions
diff --git a/pkg/subsystem/match/coincidence.go b/pkg/subsystem/match/coincidence.go
new file mode 100644
index 000000000..45bd98884
--- /dev/null
+++ b/pkg/subsystem/match/coincidence.go
@@ -0,0 +1,55 @@
+// Copyright 2023 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 match
+
+import "github.com/google/syzkaller/pkg/subsystem/entity"
+
+// CoincidenceMatrix represents a matrix that, for every pair of subsystems
+// A and B, stores the number of times A and B have coincided in the input data.
+// So far we only need it for entity.Subsystem, so its interface is tailored to it.
+type CoincidenceMatrix struct {
+ matrix map[*entity.Subsystem]map[*entity.Subsystem]int
+}
+
+func MakeCoincidenceMatrix() *CoincidenceMatrix {
+ return &CoincidenceMatrix{
+ matrix: make(map[*entity.Subsystem]map[*entity.Subsystem]int),
+ }
+}
+
+func (cm *CoincidenceMatrix) Record(items ...*entity.Subsystem) {
+ for i := 0; i < len(items); i++ {
+ for j := 0; j < len(items); j++ {
+ cm.inc(items[i], items[j])
+ }
+ }
+}
+
+func (cm *CoincidenceMatrix) Count(a *entity.Subsystem) int {
+ return cm.Get(a, a)
+}
+
+func (cm *CoincidenceMatrix) Get(a, b *entity.Subsystem) int {
+ return cm.matrix[a][b]
+}
+
+func (cm *CoincidenceMatrix) NonEmptyPairs(cb func(a, b *entity.Subsystem, val int)) {
+ for a, sub := range cm.matrix {
+ for b, val := range sub {
+ if a == b {
+ continue
+ }
+ cb(a, b, val)
+ }
+ }
+}
+
+func (cm *CoincidenceMatrix) inc(a, b *entity.Subsystem) {
+ subMatrix, ok := cm.matrix[a]
+ if !ok {
+ subMatrix = make(map[*entity.Subsystem]int)
+ cm.matrix[a] = subMatrix
+ }
+ subMatrix[b]++
+}
diff --git a/pkg/subsystem/match/coincidence_test.go b/pkg/subsystem/match/coincidence_test.go
new file mode 100644
index 000000000..15f0dd241
--- /dev/null
+++ b/pkg/subsystem/match/coincidence_test.go
@@ -0,0 +1,40 @@
+// Copyright 2023 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 match
+
+import (
+ "testing"
+
+ "github.com/google/syzkaller/pkg/subsystem/entity"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestCoincidenceMatrix(t *testing.T) {
+ cm := MakeCoincidenceMatrix()
+ a, b, c := &entity.Subsystem{}, &entity.Subsystem{}, &entity.Subsystem{}
+ cm.Record(a, b)
+ cm.Record(b, c)
+
+ // Test counts.
+ assert.Equal(t, cm.Count(a), 1)
+ assert.Equal(t, cm.Count(b), 2)
+ assert.Equal(t, cm.Count(c), 1)
+
+ // Test pairwise counts.
+ assert.Equal(t, cm.Get(a, b), 1)
+ assert.Equal(t, cm.Get(b, c), 1)
+ assert.Equal(t, cm.Get(a, c), 0)
+
+ // Test the iterator.
+ type pair struct {
+ a *entity.Subsystem
+ b *entity.Subsystem
+ }
+ expected := []pair{{a, b}, {b, a}, {b, c}, {c, b}}
+ got := []pair{}
+ cm.NonEmptyPairs(func(a, b *entity.Subsystem, _ int) {
+ got = append(got, pair{a, b})
+ })
+ assert.ElementsMatch(t, expected, got)
+}
diff --git a/pkg/subsystem/match/path_coincidence.go b/pkg/subsystem/match/path_coincidence.go
new file mode 100644
index 000000000..944546490
--- /dev/null
+++ b/pkg/subsystem/match/path_coincidence.go
@@ -0,0 +1,68 @@
+// Copyright 2023 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 match
+
+import (
+ "io/fs"
+ "regexp"
+ "runtime"
+ "sync"
+
+ "github.com/google/syzkaller/pkg/subsystem/entity"
+)
+
+func BuildCoincidenceMatrix(root fs.FS, list []*entity.Subsystem,
+ excludeRe *regexp.Regexp) (*CoincidenceMatrix, error) {
+ // Create a matcher.
+ matcher := MakePathMatcher(list)
+ chPaths, chResult := extractSubsystems(matcher)
+ // The final consumer goroutine.
+ cm := MakeCoincidenceMatrix()
+ ready := make(chan struct{})
+ go func() {
+ for items := range chResult {
+ cm.Record(items...)
+ }
+ ready <- struct{}{}
+ }()
+ // Source of data.
+ err := fs.WalkDir(root, ".", func(path string, info fs.DirEntry, err error) error {
+ if err != nil || info.IsDir() {
+ return err
+ }
+ if !includePathRe.MatchString(path) ||
+ (excludeRe != nil && excludeRe.MatchString(path)) {
+ return nil
+ }
+ chPaths <- path
+ return nil
+ })
+ close(chPaths)
+ <-ready
+ return cm, err
+}
+
+var (
+ includePathRe = regexp.MustCompile(`(?:/|\.(?:c|h|S))$`)
+)
+
+func extractSubsystems(matcher *PathMatcher) (chan<- string, <-chan []*entity.Subsystem) {
+ procs := runtime.NumCPU()
+ paths, output := make(chan string, procs), make(chan []*entity.Subsystem, procs)
+ var wg sync.WaitGroup
+ for i := 0; i < procs; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for path := range paths {
+ output <- matcher.Match(path)
+ }
+ }()
+ }
+ go func() {
+ wg.Wait()
+ close(output)
+ }()
+ return paths, output
+}
diff --git a/pkg/subsystem/match/path_coincidence_test.go b/pkg/subsystem/match/path_coincidence_test.go
new file mode 100644
index 000000000..2d1969d7f
--- /dev/null
+++ b/pkg/subsystem/match/path_coincidence_test.go
@@ -0,0 +1,49 @@
+// Copyright 2023 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 match
+
+import (
+ "testing"
+ "testing/fstest"
+
+ "github.com/google/syzkaller/pkg/subsystem/entity"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestBuildCoincidenceMatrix(t *testing.T) {
+ vfs := &entity.Subsystem{PathRules: []entity.PathRule{
+ {IncludeRegexp: `^fs/`},
+ }}
+ ext4 := &entity.Subsystem{PathRules: []entity.PathRule{
+ {IncludeRegexp: `^fs/ext4/`},
+ }}
+ ntfs := &entity.Subsystem{PathRules: []entity.PathRule{
+ {IncludeRegexp: `^fs/ntfs/`},
+ }}
+ kernel := &entity.Subsystem{PathRules: []entity.PathRule{
+ {IncludeRegexp: `.*`},
+ }}
+
+ fs := fstest.MapFS{
+ ".git/obj/12345": {},
+ "fs/inode.c": {},
+ "fs/ext4/file.c": {},
+ "fs/ntfs/file.c": {},
+ "fs/fat/file.c": {},
+ "net/socket.c": {},
+ }
+ matrix, err := BuildCoincidenceMatrix(fs, []*entity.Subsystem{vfs, ntfs, ext4, kernel}, nil)
+ assert.NoError(t, err)
+
+ // Test total counts.
+ assert.Equal(t, 5, matrix.Count(kernel))
+ assert.Equal(t, 4, matrix.Count(vfs))
+ assert.Equal(t, 1, matrix.Count(ext4))
+
+ // Test pairwise counts.
+ assert.Equal(t, 1, matrix.Get(vfs, ext4))
+ assert.Equal(t, 1, matrix.Get(vfs, ntfs))
+ assert.Equal(t, 0, matrix.Get(ext4, ntfs))
+ assert.Equal(t, 4, matrix.Get(kernel, vfs))
+}