Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dashboard/app/linux_reporting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func TestFsSubsystemFlow(t *testing.T) {
// The subsystem should have been taken from the guilty path.
c.expectEQ(reply.Subject, "[syzbot] [nilfs?] WARNING in nilfs_dat_commit_end")
assert.ElementsMatch(t, reply.To, []string{
"slava@dubeyko.com",
"konishi.ryusuke@gmail.com",
"linux-kernel@vger.kernel.org",
"linux-nilfs@vger.kernel.org",
Expand Down
5 changes: 5 additions & 0 deletions pkg/subsystem/entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,8 @@ type PathRule struct {
func (pr *PathRule) IsEmpty() bool {
return pr.IncludeRegexp == "" && pr.ExcludeRegexp == ""
}

type DebugInfo struct {
ParentChildComment map[*Subsystem]map[*Subsystem]string
FileLists map[*Subsystem][]string
}
5 changes: 3 additions & 2 deletions pkg/subsystem/linux/maintainers.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ var (

func wildcardToRegexp(wildcard string, store *strings.Builder) {
store.WriteByte('^')

// We diverge a bit from the standard MAINTAINERS rule semantics.
// path/* corresponds to the files belonging to the `path` folder,
// but, since we also infer the parent-child relationship, it's
Expand All @@ -231,7 +230,9 @@ func wildcardToRegexp(wildcard string, store *strings.Builder) {
if tokenStart < len(wildcard) {
store.WriteString(regexp.QuoteMeta(wildcard[tokenStart:]))
}
// get_maintainers.pl script tolerates the absence of / when the wildcard
// points to a folder. Let's do the same.
if wildcard == "" || wildcard[len(wildcard)-1] != '/' {
store.WriteByte('$')
store.WriteString("(?:" + escapedSeparator + "|$)")
}
}
5 changes: 3 additions & 2 deletions pkg/subsystem/linux/maintainers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,13 @@ func TestRecordToPathRule(t *testing.T) {
`with-subfolders/a`,
`with-subfolders/a/b`,
`dir/only-one`,
`dir/only-one/a.c`,
`dir/only-one/a/b.c`,
`also-with-subfolders/a.c`,
`also-with-subfolders/b/a.c`,
},
noMatch: []string{
`dir/only-one/a.c`,
`dir/only-one/a/b.c`,
`dir/only-one-plus-suffix`,
},
},
{
Expand Down
37 changes: 28 additions & 9 deletions pkg/subsystem/linux/parents.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,61 @@

package linux

import "github.com/google/syzkaller/pkg/subsystem"
import (
"fmt"

"github.com/google/syzkaller/pkg/subsystem"
)

// parentTransformations applies all subsystem list transformations that have been implemented.
func parentTransformations(matrix *CoincidenceMatrix,
list []*subsystem.Subsystem) ([]*subsystem.Subsystem, error) {
list []*subsystem.Subsystem) ([]*subsystem.Subsystem, parentInfo, error) {
list = dropSmallSubsystems(matrix, list)
list = dropDuplicateSubsystems(matrix, list)
err := setParents(matrix, list)
info, err := setParents(matrix, list)
if err != nil {
return nil, err
return nil, nil, err
}
return list, info, nil
}

type parentInfo map[*subsystem.Subsystem]map[*subsystem.Subsystem]string

func (pi parentInfo) Save(parent, child *subsystem.Subsystem, info string) {
if pi[parent] == nil {
pi[parent] = map[*subsystem.Subsystem]string{}
}
return list, nil
pi[parent][child] = info
}

// setParents attempts to determine the parent-child relations among the extracted subsystems.
// We assume A is a child of B if:
// 1) B covers more paths than A.
// 2) Most of the paths that relate to A also relate to B.
func setParents(matrix *CoincidenceMatrix, list []*subsystem.Subsystem) error {
func setParents(matrix *CoincidenceMatrix, list []*subsystem.Subsystem) (parentInfo, error) {
// Some subsystems might have already been dropeed.
inInput := map[*subsystem.Subsystem]bool{}
for _, item := range list {
inInput[item] = true
}
matrix.NonEmptyPairs(func(a, b *subsystem.Subsystem, count int) {
info := parentInfo{}
matrix.NonEmptyPairs(func(a, b *subsystem.Subsystem, common int) {
if !inInput[a] || !inInput[b] {
return
}
childFiles := matrix.Count(a)
parentFiles := matrix.Count(b)
// Demand that >= 50% paths are related.
if 2*count/matrix.Count(a) >= 1 && matrix.Count(a) < matrix.Count(b) {
if 2*common/childFiles >= 1 && childFiles < parentFiles {
a.Parents = append(a.Parents, b)
info.Save(b, a,
fmt.Sprintf("Auto-inferred: %d common files among %d/%d.",
common, childFiles, parentFiles))
a.ReachableParents() // make sure we haven't created a loop
}
})
transitiveReduction(list)
return nil
return info, nil
}

// dropSmallSubsystems removes subsystems for which we have found only a few matches in the filesystem tree.
Expand Down
4 changes: 2 additions & 2 deletions pkg/subsystem/linux/parents_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,13 @@ func TestSetParents(t *testing.T) {
"drivers/android/binder.c": {},
}

matrix, err := BuildCoincidenceMatrix(tree,
matrix, _, err := BuildCoincidenceMatrix(tree,
[]*subsystem.Subsystem{kernel, net, wireless, drivers}, nil)
assert.NoError(t, err)

// Calculate parents.

err = setParents(matrix, []*subsystem.Subsystem{kernel, net, wireless, drivers})
_, err = setParents(matrix, []*subsystem.Subsystem{kernel, net, wireless, drivers})
if err != nil {
t.Fatal(err)
}
Expand Down
34 changes: 27 additions & 7 deletions pkg/subsystem/linux/path_coincidence.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,27 @@ import (
"io/fs"
"regexp"
"runtime"
"sort"
"sync"

"github.com/google/syzkaller/pkg/subsystem"
)

func BuildCoincidenceMatrix(root fs.FS, list []*subsystem.Subsystem,
excludeRe *regexp.Regexp) (*CoincidenceMatrix, error) {
excludeRe *regexp.Regexp) (*CoincidenceMatrix, *matrixDebugInfo, error) {
// Create a matcher.
matcher := subsystem.MakePathMatcher(list)
chPaths, chResult := extractSubsystems(matcher)
// The final consumer goroutine.
cm := MakeCoincidenceMatrix()
ready := make(chan struct{})
debug := &matrixDebugInfo{files: map[*subsystem.Subsystem][]string{}}
go func() {
for items := range chResult {
cm.Record(items...)
for item := range chResult {
cm.Record(item.list...)
for _, entity := range item.list {
debug.files[entity] = append(debug.files[entity], item.path)
}
}
ready <- struct{}{}
}()
Expand All @@ -40,23 +45,38 @@ func BuildCoincidenceMatrix(root fs.FS, list []*subsystem.Subsystem,
})
close(chPaths)
<-ready
return cm, err
for _, list := range debug.files {
sort.Strings(list)
}
return cm, debug, err
}

type matrixDebugInfo struct {
files map[*subsystem.Subsystem][]string
}

var (
includePathRe = regexp.MustCompile(`(?:/|\.(?:c|h|S))$`)
)

func extractSubsystems(matcher *subsystem.PathMatcher) (chan<- string, <-chan []*subsystem.Subsystem) {
type extracted struct {
path string
list []*subsystem.Subsystem
}

func extractSubsystems(matcher *subsystem.PathMatcher) (chan<- string, <-chan extracted) {
procs := runtime.NumCPU()
paths, output := make(chan string, procs), make(chan []*subsystem.Subsystem, procs)
paths, output := make(chan string, procs), make(chan extracted, 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)
output <- extracted{
path: path,
list: matcher.Match(path),
}
}
}()
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/subsystem/linux/path_coincidence_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestBuildCoincidenceMatrix(t *testing.T) {
"fs/fat/file.c": {},
"net/socket.c": {},
}
matrix, err := BuildCoincidenceMatrix(fs, []*subsystem.Subsystem{vfs, ntfs, ext4, kernel}, nil)
matrix, _, err := BuildCoincidenceMatrix(fs, []*subsystem.Subsystem{vfs, ntfs, ext4, kernel}, nil)
assert.NoError(t, err)

// Test total counts.
Expand Down
3 changes: 2 additions & 1 deletion pkg/subsystem/linux/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ var (
// - syz_mount_image$vxfs
// - syz_mount_image$zonefs

"bcachefs": {"syz_mount_image$bcachefs"},
"bfs": {"syz_mount_image$bfs"},
"bluetooth": {"syz_emit_vhci"},
"btrfs": {"syz_mount_image$btrfs"},
Expand Down Expand Up @@ -89,6 +88,8 @@ var (
"tomoyo-dev-en@lists.osdn.me": {},
"tomoyo-users-en@lists.osdn.me": {},
"kernel@collabora.com": {},
// The list is too broad and always intersects with other lists.
"x86@kernel.org": {},
},
extraSubsystems: map[string][]string{
"bfs": {"BFS FILE SYSTEM"},
Expand Down
26 changes: 15 additions & 11 deletions pkg/subsystem/linux/subsystems.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ import (
"github.com/google/syzkaller/pkg/subsystem"
)

func ListFromRepo(repo string) ([]*subsystem.Subsystem, error) {
func ListFromRepo(repo string) ([]*subsystem.Subsystem, *subsystem.DebugInfo, error) {
return listFromRepoInner(os.DirFS(repo), linuxSubsystemRules)
}

// listFromRepoInner allows for better testing.
func listFromRepoInner(root fs.FS, rules *customRules) ([]*subsystem.Subsystem, error) {
func listFromRepoInner(root fs.FS, rules *customRules) ([]*subsystem.Subsystem,
*subsystem.DebugInfo, error) {
records, err := getMaintainers(root)
if err != nil {
return nil, err
return nil, nil, err
}
removeMatchingPatterns(records, dropPatterns)
ctx := &linuxCtx{
Expand All @@ -31,22 +32,22 @@ func listFromRepoInner(root fs.FS, rules *customRules) ([]*subsystem.Subsystem,
}
extraList, err := ctx.groupByRules()
if err != nil {
return nil, err
return nil, nil, err
}
list := append(ctx.groupByList(), extraList...)
matrix, err := BuildCoincidenceMatrix(root, list, dropPatterns)
matrix, matrixDebug, err := BuildCoincidenceMatrix(root, list, dropPatterns)
if err != nil {
return nil, err
return nil, nil, err
}
list, err = parentTransformations(matrix, list)
list, parentDebug, err := parentTransformations(matrix, list)
if err != nil {
return nil, err
return nil, nil, err
}
if err := setSubsystemNames(list); err != nil {
return nil, fmt.Errorf("failed to set names: %w", err)
return nil, nil, fmt.Errorf("failed to set names: %w", err)
}
if err := ctx.applyExtraRules(list); err != nil {
return nil, fmt.Errorf("failed to apply extra rules: %w", err)
return nil, nil, fmt.Errorf("failed to apply extra rules: %w", err)
}

// Sort subsystems by name to keep output consistent.
Expand All @@ -61,7 +62,10 @@ func listFromRepoInner(root fs.FS, rules *customRules) ([]*subsystem.Subsystem,
return a.ExcludeRegexp < b.ExcludeRegexp
})
}
return list, nil
return list, &subsystem.DebugInfo{
ParentChildComment: parentDebug,
FileLists: matrixDebug.files,
}, nil
}

type linuxCtx struct {
Expand Down
10 changes: 5 additions & 5 deletions pkg/subsystem/linux/subsystems_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

func TestGroupLinuxSubsystems(t *testing.T) {
subsystems, err := listFromRepoInner(
subsystems, _, err := listFromRepoInner(
prepareTestLinuxRepo(t, []byte(testMaintainers)),
nil)
if err != nil {
Expand Down Expand Up @@ -57,7 +57,7 @@ func TestGroupLinuxSubsystems(t *testing.T) {
}

func TestCustomCallRules(t *testing.T) {
subsystems, err := listFromRepoInner(
subsystems, _, err := listFromRepoInner(
prepareTestLinuxRepo(t, []byte(testMaintainers)),
testRules,
)
Expand Down Expand Up @@ -103,7 +103,7 @@ func TestLinuxSubsystemPaths(t *testing.T) {
// For the list of subsystems, see TestLinuxSubsystemsList.
// Here we rely on the same ones.
repo := prepareTestLinuxRepo(t, []byte(testMaintainers))
subsystems, err := listFromRepoInner(repo, nil)
subsystems, _, err := listFromRepoInner(repo, nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -164,7 +164,7 @@ func TestLinuxSubsystemParents(t *testing.T) {
// For the list of subsystems, see TestLinuxSubsystemsList.
// Here we rely on the same ones.
repo := prepareTestLinuxRepo(t, []byte(testMaintainers))
subsystems, err := listFromRepoInner(repo, nil)
subsystems, _, err := listFromRepoInner(repo, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -177,7 +177,7 @@ func TestLinuxSubsystemParents(t *testing.T) {
})

// Now check that our custom parent rules work.
subsystems2, err := listFromRepoInner(repo, &customRules{
subsystems2, _, err := listFromRepoInner(repo, &customRules{
addParents: map[string][]string{
// Just for the sake of testing.
"fs": {"mm"},
Expand Down
Loading
Loading