diff --git a/pkg/email/lore/parse.go b/pkg/email/lore/parse.go index 3b044bf21f78..c8abfcc4730e 100644 --- a/pkg/email/lore/parse.go +++ b/pkg/email/lore/parse.go @@ -25,12 +25,13 @@ type Thread struct { // Series represents a single patch series sent over email. type Series struct { - Subject string - MessageID string - Version int - Corrupted string // If non-empty, contains a reason why the series better be ignored. - Tags []string - Patches []Patch + Subject string + MessageID string + Version int + Corrupted string // If non-empty, contains a reason why the series better be ignored. + Tags []string + Patches []Patch + BaseCommitHint string } type Patch struct { @@ -88,6 +89,13 @@ func PatchSeries(emails []*Email) []*Series { if !ok { continue } + if series.BaseCommitHint == "" { // Usually base-commit is in patch 0 or 1. Check them all to be safe. + regex := regexp.MustCompile(`(?m)^base-commit:\s*([0-9a-fA-F]{40})$`) + matches := regex.FindStringSubmatch(email.Body) + if len(matches) >= 2 { + series.BaseCommitHint = matches[1] + } + } seq := patch.Seq.ValueOr(1) if seq == 0 { // The cover email is not of interest. diff --git a/syz-cluster/pkg/api/api.go b/syz-cluster/pkg/api/api.go index 205180bbbe88..c1def150b276 100644 --- a/syz-cluster/pkg/api/api.go +++ b/syz-cluster/pkg/api/api.go @@ -127,16 +127,17 @@ type NewFinding struct { } type Series struct { - ID string `json:"id"` // Only included in the reply. - ExtID string `json:"ext_id"` - Title string `json:"title"` - AuthorEmail string `json:"author_email"` - Cc []string `json:"cc"` - Version int `json:"version"` - Link string `json:"link"` - SubjectTags []string `json:"subject_tags"` - PublishedAt time.Time `json:"published_at"` - Patches []SeriesPatch `json:"patches"` + ID string `json:"id"` // Only included in the reply. + ExtID string `json:"ext_id"` + Title string `json:"title"` + AuthorEmail string `json:"author_email"` + Cc []string `json:"cc"` + Version int `json:"version"` + Link string `json:"link"` + SubjectTags []string `json:"subject_tags"` + PublishedAt time.Time `json:"published_at"` + Patches []SeriesPatch `json:"patches"` + BaseCommitHint string `json:"base_commit"` } func (s *Series) PatchBodies() [][]byte { diff --git a/syz-cluster/pkg/db/entities.go b/syz-cluster/pkg/db/entities.go index 563d7e4ecbb1..a920f5ca7e7e 100644 --- a/syz-cluster/pkg/db/entities.go +++ b/syz-cluster/pkg/db/entities.go @@ -10,13 +10,14 @@ import ( ) type Series struct { - ID string `spanner:"ID"` - ExtID string `spanner:"ExtID"` - AuthorName string `spanner:"AuthorName"` - AuthorEmail string `spanner:"AuthorEmail"` - Title string `spanner:"Title"` - Link string `spanner:"Link"` - Version int64 `spanner:"Version"` + ID string `spanner:"ID"` + ExtID string `spanner:"ExtID"` + AuthorName string `spanner:"AuthorName"` + AuthorEmail string `spanner:"AuthorEmail"` + Title string `spanner:"Title"` + Link string `spanner:"Link"` + Version int64 `spanner:"Version"` + BaseCommitHint string `spanner:"BaseCommitHint"` // In LKML patches, there are often hints at the target tree for the patch. SubjectTags []string `spanner:"SubjectTags"` PublishedAt time.Time `spanner:"PublishedAt"` diff --git a/syz-cluster/pkg/db/migrations/9_add_series_base_commit.down.sql b/syz-cluster/pkg/db/migrations/9_add_series_base_commit.down.sql new file mode 100644 index 000000000000..96666a740e16 --- /dev/null +++ b/syz-cluster/pkg/db/migrations/9_add_series_base_commit.down.sql @@ -0,0 +1 @@ +ALTER TABLE Series DROP COLUMN BaseCommitHint; diff --git a/syz-cluster/pkg/db/migrations/9_add_series_base_commit.up.sql b/syz-cluster/pkg/db/migrations/9_add_series_base_commit.up.sql new file mode 100644 index 000000000000..569559221d5d --- /dev/null +++ b/syz-cluster/pkg/db/migrations/9_add_series_base_commit.up.sql @@ -0,0 +1 @@ +ALTER TABLE Series ADD COLUMN BaseCommitHint STRING(256); diff --git a/syz-cluster/pkg/service/series.go b/syz-cluster/pkg/service/series.go index d1ace9b098be..a16f9be77420 100644 --- a/syz-cluster/pkg/service/series.go +++ b/syz-cluster/pkg/service/series.go @@ -54,14 +54,15 @@ func (s *SeriesService) getSessionSeries(ctx context.Context, sessionID string, func (s *SeriesService) UploadSeries(ctx context.Context, series *api.Series) (*api.UploadSeriesResp, error) { seriesObj := &db.Series{ - ID: uuid.NewString(), - ExtID: series.ExtID, - AuthorEmail: series.AuthorEmail, - Title: series.Title, - Version: int64(series.Version), - Link: series.Link, - PublishedAt: series.PublishedAt, - Cc: series.Cc, + ID: uuid.NewString(), + ExtID: series.ExtID, + AuthorEmail: series.AuthorEmail, + Title: series.Title, + Version: int64(series.Version), + Link: series.Link, + PublishedAt: series.PublishedAt, + Cc: series.Cc, + BaseCommitHint: series.BaseCommitHint, } for _, tag := range series.SubjectTags { const tageSizeLimit = 511 @@ -120,15 +121,16 @@ func (s *SeriesService) getSeries(ctx context.Context, return nil, fmt.Errorf("failed to fetch patches: %w", err) } ret := &api.Series{ - ID: series.ID, - ExtID: series.ExtID, - Title: series.Title, - AuthorEmail: series.AuthorEmail, - Version: int(series.Version), - Cc: series.Cc, - PublishedAt: series.PublishedAt, - Link: series.Link, - SubjectTags: series.SubjectTags, + ID: series.ID, + ExtID: series.ExtID, + Title: series.Title, + AuthorEmail: series.AuthorEmail, + Version: int(series.Version), + Cc: series.Cc, + PublishedAt: series.PublishedAt, + Link: series.Link, + SubjectTags: series.SubjectTags, + BaseCommitHint: series.BaseCommitHint, } for _, patch := range patches { var body []byte diff --git a/syz-cluster/pkg/triage/commit.go b/syz-cluster/pkg/triage/commit.go index 81c7f6150d4d..00fd5514b065 100644 --- a/syz-cluster/pkg/triage/commit.go +++ b/syz-cluster/pkg/triage/commit.go @@ -81,3 +81,15 @@ func (cs *CommitSelector) Select(series *api.Series, tree *api.Tree, lastBuild * } return SelectResult{Reason: reasonNotApplies}, nil } + +func (cs *CommitSelector) TrySelectWithHint(series *api.Series) (SelectResult, error) { + if series.BaseCommitHint == "" { + return SelectResult{}, nil + } + var git GitTreeOps + baseCommit, err := git.Git.Commit(series.BaseCommitHint) + if err != nil { + return SelectResult{}, err + } + return SelectResult{Commit: baseCommit.Hash}, nil +} diff --git a/syz-cluster/series-tracker/main.go b/syz-cluster/series-tracker/main.go index 4f57f99cfede..2442b7c57ad6 100644 --- a/syz-cluster/series-tracker/main.go +++ b/syz-cluster/series-tracker/main.go @@ -154,13 +154,14 @@ func (sf *SeriesFetcher) handleSeries(ctx context.Context, series *lore.Series, date = time.Now() } apiSeries := &api.Series{ - ExtID: series.MessageID, - AuthorEmail: first.Author, - Title: series.Subject, - Version: series.Version, - SubjectTags: series.Tags, - Link: loreLink(series.MessageID), - PublishedAt: date, + ExtID: series.MessageID, + AuthorEmail: first.Author, + Title: series.Subject, + Version: series.Version, + SubjectTags: series.Tags, + Link: loreLink(series.MessageID), + PublishedAt: date, + BaseCommitHint: series.BaseCommitHint, } sp := seriesProcessor{} for i, patch := range series.Patches { diff --git a/syz-cluster/workflow/triage-step/main.go b/syz-cluster/workflow/triage-step/main.go index 37eddd5e82c2..342934a15d33 100644 --- a/syz-cluster/workflow/triage-step/main.go +++ b/syz-cluster/workflow/triage-step/main.go @@ -113,6 +113,24 @@ func (triager *seriesTriager) GetVerdict(ctx context.Context, sessionID string) func (triager *seriesTriager) prepareFuzzingTask(ctx context.Context, series *api.Series, trees []*api.Tree, target *triage.MergedFuzzConfig) (*api.FuzzTask, error) { var skipErr error + selector := triage.NewCommitSelector(triager.ops, triager.DebugTracer) + + // First try to use hints from the series description. + if series.BaseCommitHint != "" { + for _, tree := range trees { + triager.Log("considering tree %q with a hint", tree.Name) + result, err := selector.TrySelectWithHint(series) + if err != nil { + return nil, fmt.Errorf("failed to run TrySelectWithHint for %q: %w", tree.Name, err) + } + if result.Commit != "" { + triager.Log("selected base commit with hint: %s", result.Commit) + return buildFuzzTask(series, tree, target, result.Commit), nil + } + triager.Log("failed to find a base commit with hint for %q: %s", tree.Name, result.Reason) + } + } + for _, tree := range trees { triager.Log("considering tree %q", tree.Name) arch := "amd64" @@ -127,7 +145,6 @@ func (triager *seriesTriager) prepareFuzzingTask(ctx context.Context, series *ap return nil, fmt.Errorf("failed to query the last build for %q: %w", tree.Name, err) } triager.Log("%q's last build: %q", tree.Name, lastBuild) - selector := triage.NewCommitSelector(triager.ops, triager.DebugTracer) result, err := selector.Select(series, tree, lastBuild) if err != nil { // TODO: the workflow step must be retried. @@ -141,24 +158,29 @@ func (triager *seriesTriager) prepareFuzzingTask(ctx context.Context, series *ap continue } triager.Log("selected base commit: %s", result.Commit) - base := api.BuildRequest{ - TreeName: tree.Name, - TreeURL: tree.URL, - ConfigName: target.KernelConfig, - CommitHash: result.Commit, - Arch: arch, - } - fuzz := &api.FuzzTask{ - Base: base, - Patched: base, - FuzzConfig: *target.FuzzConfig, - } - fuzz.Patched.SeriesID = series.ID - return fuzz, nil + return buildFuzzTask(series, tree, target, result.Commit), nil } return nil, skipErr } +func buildFuzzTask(series *api.Series, tree *api.Tree, target *triage.MergedFuzzConfig, commit string) *api.FuzzTask { + arch := "amd64" + base := api.BuildRequest{ + TreeName: tree.Name, + TreeURL: tree.URL, + ConfigName: target.KernelConfig, + CommitHash: commit, + Arch: arch, + } + fuzz := &api.FuzzTask{ + Base: base, + Patched: base, + FuzzConfig: *target.FuzzConfig, + } + fuzz.Patched.SeriesID = series.ID + return fuzz +} + type SkipTriageError struct { Reason error }