From 2246b3b0ea578783224118ca3af660ecb0ebd2b6 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Wed, 11 Jan 2023 19:36:30 +0100 Subject: pkg/subsystem/linux: convert MAINTAINERS patters to regexps Take care of the corner cases and add extensive tests. --- pkg/subsystem/linux/maintainers.go | 68 ++++++++++++++++ pkg/subsystem/linux/maintainers_test.go | 134 ++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) (limited to 'pkg/subsystem') diff --git a/pkg/subsystem/linux/maintainers.go b/pkg/subsystem/linux/maintainers.go index ce692bfd4..6a188b261 100644 --- a/pkg/subsystem/linux/maintainers.go +++ b/pkg/subsystem/linux/maintainers.go @@ -8,9 +8,12 @@ import ( "fmt" "io" "net/mail" + "path/filepath" "regexp" "strings" "unicode" + + "github.com/google/syzkaller/pkg/subsystem/entity" ) // maintainersRecord represents a single raw record in the MAINTAINERS file. @@ -142,3 +145,68 @@ func parseEmail(value string) (string, error) { } return addr.Address, nil } + +func (r maintainersRecord) ToPathRule() entity.PathRule { + inclRe := strings.Builder{} + for i, wildcard := range r.includePatterns { + if i > 0 { + inclRe.WriteByte('|') + } + wildcardToRegexp(wildcard, &inclRe) + } + for _, rg := range r.regexps { + if inclRe.Len() > 0 { + inclRe.WriteByte('|') + } + inclRe.WriteString(rg) + } + exclRe := strings.Builder{} + for i, wildcard := range r.excludePatterns { + if i > 0 { + exclRe.WriteByte('|') + } + wildcardToRegexp(wildcard, &exclRe) + } + return entity.PathRule{ + IncludeRegexp: inclRe.String(), + ExcludeRegexp: exclRe.String(), + } +} + +var ( + escapedSeparator = regexp.QuoteMeta(fmt.Sprintf("%c", filepath.Separator)) + wildcardReplace = map[byte]string{ + '*': `[^` + escapedSeparator + `]*`, + '?': `.`, + '/': escapedSeparator, + } +) + +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 + // easier to make it cover the whole subtree. + if len(wildcard) >= 2 && wildcard[len(wildcard)-2:] == "/*" { + wildcard = wildcard[:len(wildcard)-1] + } + + tokenStart := 0 + for i, c := range wildcard { + replace, exists := wildcardReplace[byte(c)] + if !exists { + continue + } + store.WriteString(regexp.QuoteMeta(wildcard[tokenStart:i])) + store.WriteString(replace) + tokenStart = i + 1 + } + if tokenStart < len(wildcard) { + store.WriteString(regexp.QuoteMeta(wildcard[tokenStart:])) + } + if wildcard == "" || wildcard[len(wildcard)-1] != '/' { + store.WriteByte('$') + } +} diff --git a/pkg/subsystem/linux/maintainers_test.go b/pkg/subsystem/linux/maintainers_test.go index 874bdcaa7..dc3299dbf 100644 --- a/pkg/subsystem/linux/maintainers_test.go +++ b/pkg/subsystem/linux/maintainers_test.go @@ -8,8 +8,142 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/google/syzkaller/pkg/subsystem/entity" + "github.com/google/syzkaller/pkg/subsystem/match" ) +func TestRecordToPathRule(t *testing.T) { + tests := []struct { + name string + record maintainersRecord + match []string + noMatch []string + }{ + { + name: `general test`, + record: maintainersRecord{ + includePatterns: []string{ + `drivers/gpio/gpio-*wm*.c`, + `drivers/hwmon/wm83??-hwmon.c`, + `include/linux/mfd/arizona/`, + `include/linux/wm97xx.h`, + }, + }, + match: []string{ + `drivers/gpio/gpio-wm831x.c`, + `drivers/gpio/gpio-abcdwm831x.c`, + `drivers/hwmon/wm8355-hwmon.c`, + `include/linux/mfd/arizona/file.c`, + `include/linux/mfd/arizona/subfolder/file.c`, + `include/linux/wm97xx.h`, + }, + noMatch: []string{ + `drivers/gpio/gpio-w831x.c`, + `drivers/hwmon/wm83556-hwmon.c`, + `drivers/hwmon/wm831-hwmon.c`, + `include/linux/mfd`, + `include`, + `random-file`, + }, + }, + { + name: `include patterns and regexp`, + record: maintainersRecord{ + includePatterns: []string{`drivers/rtc/rtc-opal.c`}, + regexps: []string{`[^a-z0-9]ps3`}, + }, + match: []string{ + `drivers/rtc/rtc-opal.c`, + `drivers/ps3/a.c`, + `drivers/sub/ps3/a.c`, + `drivers/sub/sub/ps3.c`, + }, + noMatch: []string{ + `drivers/aps3/a.c`, + `drivers/abc/aps3.c`, + }, + }, + { + name: `exclude patterns`, + record: maintainersRecord{ + includePatterns: []string{`security/`}, + excludePatterns: []string{`security/selinux/`}, + }, + match: []string{ + `security/apparmor/abcd.c`, + `security/abcd.c`, + }, + noMatch: []string{ + `security/selinux/abcd.c`, + }, + }, + { + name: `handle / at the end`, + record: maintainersRecord{ + includePatterns: []string{ + `with-subfolders/`, + `dir/only-one`, + `also-with-subfolders/*`, + }, + }, + match: []string{ + `with-subfolders/a`, + `with-subfolders/a/b`, + `dir/only-one`, + `also-with-subfolders/a.c`, + `also-with-subfolders/b/a.c`, + }, + noMatch: []string{ + `dir/only-one/a.c`, + `dir/only-one/a/b.c`, + }, + }, + { + name: `wildcards are well escaped`, + record: maintainersRecord{ + includePatterns: []string{`drivers/net/ethernet/smsc/smc91x.*`}, + }, + match: []string{ + `drivers/net/ethernet/smsc/smc91x.c`, + `drivers/net/ethernet/smsc/smc91x.h`, + }, + noMatch: []string{ + `drivers/net/ethernet/smsc/smc91xAh`, + }, + }, + { + name: `match everything`, + record: maintainersRecord{ + includePatterns: []string{`*`, `*/`}, + }, + match: []string{ + `a`, + `a/b`, + `a/b/c`, + }, + }, + } + + for _, loopTest := range tests { + test := loopTest + t.Run(test.name, func(t *testing.T) { + pm := match.MakePathMatcher([]*entity.Subsystem{ + {PathRules: []entity.PathRule{test.record.ToPathRule()}}, + }) + for _, path := range test.match { + if len(pm.Match(path)) != 1 { + t.Fatalf("did not match %#v", path) + } + } + for _, path := range test.noMatch { + if len(pm.Match(path)) > 0 { + t.Fatalf("matched %#v", path) + } + } + }) + } +} + func TestLinuxMaintainers(t *testing.T) { result, err := parseLinuxMaintainers( strings.NewReader(maintainersSample), -- cgit mrf-deployment