github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-cluster/workflow/triage-step/main.go (about) 1 // Copyright 2024 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package main 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "flag" 11 "fmt" 12 13 "github.com/google/syzkaller/pkg/debugtracer" 14 "github.com/google/syzkaller/pkg/osutil" 15 "github.com/google/syzkaller/syz-cluster/pkg/api" 16 "github.com/google/syzkaller/syz-cluster/pkg/app" 17 "github.com/google/syzkaller/syz-cluster/pkg/triage" 18 ) 19 20 var ( 21 flagSession = flag.String("session", "", "session ID") 22 flagRepo = flag.String("repository", "", "path to a kernel checkout") 23 flagVerdict = flag.String("verdict", "", "where to save the verdict") 24 ) 25 26 func main() { 27 flag.Parse() 28 if *flagSession == "" || *flagRepo == "" { 29 // TODO: abort the whole workflow, no sense to retry. Alert the error. 30 app.Fatalf("--session and --repo must be set") 31 } 32 client := app.DefaultClient() 33 repo, err := triage.NewGitTreeOps(*flagRepo, true) 34 if err != nil { 35 app.Fatalf("failed to initialize the repository: %v", err) 36 } 37 ctx := context.Background() 38 output := new(bytes.Buffer) 39 tracer := &debugtracer.GenericTracer{WithTime: true, TraceWriter: output} 40 41 triager := &seriesTriager{ 42 DebugTracer: tracer, 43 client: client, 44 ops: repo, 45 } 46 verdict, err := triager.GetVerdict(ctx, *flagSession) 47 if err != nil { 48 app.Fatalf("failed to get the verdict: %v", err) 49 } 50 err = client.UploadTriageResult(ctx, *flagSession, &api.UploadTriageResultReq{ 51 SkipReason: verdict.SkipReason, 52 Log: output.Bytes(), 53 }) 54 if err != nil { 55 app.Fatalf("failed to upload triage results: %v", err) 56 } 57 if *flagVerdict != "" { 58 osutil.WriteJSON(*flagVerdict, verdict) 59 } 60 61 // TODO: 62 // 1. It might be that the kernel builds/boots for one arch and does not build for another. 63 // 2. What if controller does not reply? Let Argo just restart the step. 64 } 65 66 type seriesTriager struct { 67 debugtracer.DebugTracer 68 client *api.Client 69 ops triage.TreeOps 70 } 71 72 func (triager *seriesTriager) GetVerdict(ctx context.Context, sessionID string) (*api.TriageResult, error) { 73 series, err := triager.client.GetSessionSeries(ctx, sessionID) 74 if err != nil { 75 // TODO: the workflow step must be retried. 76 return nil, fmt.Errorf("failed to query series: %w", err) 77 } 78 treesResp, err := triager.client.GetTrees(ctx) 79 if err != nil { 80 return nil, fmt.Errorf("failed to query trees: %w", err) 81 } 82 selectedTrees := triage.SelectTrees(series, treesResp.Trees) 83 if len(selectedTrees) == 0 { 84 return &api.TriageResult{ 85 SkipReason: "no suitable base kernel trees found", 86 }, nil 87 } 88 fuzzConfigs := triage.MergeKernelFuzzConfigs(triage.SelectFuzzConfigs(series, treesResp.FuzzTargets)) 89 if len(fuzzConfigs) == 0 { 90 return &api.TriageResult{ 91 SkipReason: "no suitable fuzz configs found", 92 }, nil 93 } 94 ret := &api.TriageResult{} 95 for _, campaign := range fuzzConfigs { 96 fuzzTask, err := triager.prepareFuzzingTask(ctx, series, selectedTrees, campaign) 97 var skipErr *SkipTriageError 98 if errors.As(err, &skipErr) { 99 ret.SkipReason = skipErr.Reason.Error() 100 continue 101 } else if err != nil { 102 return nil, err 103 } 104 ret.Fuzz = append(ret.Fuzz, fuzzTask) 105 } 106 if len(ret.Fuzz) > 0 { 107 // If we have prepared at least one fuzzing task, the series was not skipped. 108 ret.SkipReason = "" 109 } 110 return ret, nil 111 } 112 113 func (triager *seriesTriager) prepareFuzzingTask(ctx context.Context, series *api.Series, trees []*api.Tree, 114 target *triage.MergedFuzzConfig) (*api.FuzzTask, error) { 115 var skipErr error 116 for _, tree := range trees { 117 triager.Log("considering tree %q", tree.Name) 118 arch := "amd64" 119 lastBuild, err := triager.client.LastBuild(ctx, &api.LastBuildReq{ 120 Arch: arch, 121 ConfigName: target.KernelConfig, 122 TreeName: tree.Name, 123 Status: api.BuildSuccess, 124 }) 125 if err != nil { 126 // TODO: the workflow step must be retried. 127 return nil, fmt.Errorf("failed to query the last build for %q: %w", tree.Name, err) 128 } 129 triager.Log("%q's last build: %q", tree.Name, lastBuild) 130 selector := triage.NewCommitSelector(triager.ops, triager.DebugTracer) 131 result, err := selector.Select(series, tree, lastBuild) 132 if err != nil { 133 // TODO: the workflow step must be retried. 134 return nil, fmt.Errorf("failed to run the commit selector for %q: %w", tree.Name, err) 135 } else if result.Commit == "" { 136 // If we fail to find a suitable commit for all the trees, return an error just about the first one. 137 if skipErr == nil { 138 skipErr = SkipError("failed to find a base commit: " + result.Reason) 139 } 140 triager.Log("failed to find a base commit for %q", tree.Name) 141 continue 142 } 143 triager.Log("selected base commit: %s", result.Commit) 144 base := api.BuildRequest{ 145 TreeName: tree.Name, 146 TreeURL: tree.URL, 147 ConfigName: target.KernelConfig, 148 CommitHash: result.Commit, 149 Arch: arch, 150 } 151 fuzz := &api.FuzzTask{ 152 Base: base, 153 Patched: base, 154 FuzzConfig: *target.FuzzConfig, 155 } 156 fuzz.Patched.SeriesID = series.ID 157 return fuzz, nil 158 } 159 return nil, skipErr 160 } 161 162 type SkipTriageError struct { 163 Reason error 164 } 165 166 func SkipError(reason string) *SkipTriageError { 167 return &SkipTriageError{Reason: errors.New(reason)} 168 } 169 170 func (e *SkipTriageError) Error() string { 171 return fmt.Sprintf("series must be skipped: %s", e.Reason) 172 } 173 174 func (e *SkipTriageError) Unwrap() error { 175 return e.Reason 176 }