github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/kcidb/client.go (about) 1 // Copyright 2020 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 kcidb 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/json" 10 "fmt" 11 "os" 12 "os/exec" 13 "strings" 14 "time" 15 16 "cloud.google.com/go/pubsub" 17 "github.com/google/syzkaller/dashboard/dashapi" 18 "github.com/google/syzkaller/sys/targets" 19 "google.golang.org/api/option" 20 ) 21 22 type Client struct { 23 ctx context.Context 24 origin string 25 client *pubsub.Client 26 topic *pubsub.Topic 27 } 28 29 // NewClient creates a new client to send pubsub messages to Kcidb. 30 // Origin is how this system identified in Kcidb, e.g. "syzbot_foobar". 31 // Project is Kcidb GCE project name, e.g. "kernelci-production". 32 // Topic is pubsub topic to publish messages to, e.g. "playground_kernelci_new". 33 // Credentials is Google application credentials file contents to use for authorization. 34 func NewClient(ctx context.Context, origin, project, topic string, credentials []byte) (*Client, error) { 35 client, err := pubsub.NewClient(ctx, project, option.WithCredentialsJSON(credentials)) 36 if err != nil { 37 return nil, err 38 } 39 c := &Client{ 40 ctx: ctx, 41 origin: origin, 42 client: client, 43 topic: client.Topic(topic), 44 } 45 return c, err 46 } 47 48 func (c *Client) Close() error { 49 c.topic.Stop() 50 return c.client.Close() 51 } 52 53 func (c *Client) Publish(bug *dashapi.BugReport) error { 54 target := targets.List[bug.OS][bug.VMArch] 55 if target == nil { 56 return fmt.Errorf("unsupported OS/arch %v/%v", bug.OS, bug.VMArch) 57 } 58 data, err := json.MarshalIndent(c.convert(target, bug), "", " ") 59 if err != nil { 60 return fmt.Errorf("failed to marshal kcidb json: %w", err) 61 } 62 if err := kcidbValidate(data); err != nil { 63 return err 64 } 65 _, err = c.topic.Publish(c.ctx, &pubsub.Message{Data: data}).Get(c.ctx) 66 return err 67 } 68 69 var Validate bool 70 71 func kcidbValidate(data []byte) error { 72 if !Validate { 73 return nil 74 } 75 const bin = "kcidb-validate" 76 if _, err := exec.LookPath(bin); err != nil { 77 fmt.Fprintf(os.Stderr, "%v is not found\n", bin) 78 return nil 79 } 80 cmd := exec.Command(bin) 81 cmd.Stdin = bytes.NewReader(data) 82 output, err := cmd.CombinedOutput() 83 if err != nil { 84 return fmt.Errorf("%v failed (%w) on:\n%s\n\nerror: %s", 85 bin, err, data, output) 86 } 87 return nil 88 } 89 90 func (c *Client) convert(target *targets.Target, bug *dashapi.BugReport) *Kcidb { 91 res := &Kcidb{ 92 Version: &Version{ 93 Major: 3, 94 Minor: 0, 95 }, 96 Revisions: []*Revision{ 97 { 98 Origin: c.origin, 99 ID: bug.KernelCommit, 100 GitRepositoryURL: normalizeRepo(bug.KernelRepo), 101 GitCommitHash: bug.KernelCommit, 102 GitRepositoryBranch: bug.KernelBranch, 103 Description: bug.KernelCommitTitle, 104 PublishingTime: bug.KernelCommitDate.Format(time.RFC3339), 105 DiscoveryTime: bug.BuildTime.Format(time.RFC3339), 106 Valid: true, 107 }, 108 }, 109 Builds: []*Build{ 110 { 111 Origin: c.origin, 112 ID: c.extID(bug.BuildID), 113 RevisionID: bug.KernelCommit, 114 Architecture: target.KernelArch, 115 Compiler: bug.CompilerID, 116 StartTime: bug.BuildTime.Format(time.RFC3339), 117 ConfigURL: bug.KernelConfigLink, 118 Valid: true, 119 }, 120 }, 121 } 122 if strings.Contains(bug.Title, "build error") { 123 build := res.Builds[0] 124 build.Valid = false 125 build.LogURL = bug.LogLink 126 build.Misc = &BuildMisc{ 127 OriginURL: bug.Link, 128 ReportedBy: bug.CreditEmail, 129 } 130 } else { 131 var outputFiles []*Resource 132 if bug.ReportLink != "" { 133 outputFiles = append(outputFiles, &Resource{Name: "report.txt", URL: bug.ReportLink}) 134 } 135 if bug.LogLink != "" { 136 outputFiles = append(outputFiles, &Resource{Name: "log.txt", URL: bug.LogLink}) 137 } 138 if bug.ReproCLink != "" { 139 outputFiles = append(outputFiles, &Resource{Name: "repro.c", URL: bug.ReproCLink}) 140 } 141 if bug.ReproSyzLink != "" { 142 outputFiles = append(outputFiles, &Resource{Name: "repro.syz.txt", URL: bug.ReproSyzLink}) 143 } 144 if bug.MachineInfoLink != "" { 145 outputFiles = append(outputFiles, &Resource{Name: "machine_info.txt", URL: bug.MachineInfoLink}) 146 } 147 causeRevisionID := "" 148 if bug.BisectCause != nil && bug.BisectCause.Commit != nil { 149 causeRevisionID = bug.BisectCause.Commit.Hash 150 } 151 res.Tests = []*Test{ 152 { 153 Origin: c.origin, 154 ID: c.extID(bug.ID), 155 BuildID: c.extID(bug.BuildID), 156 Path: "syzkaller", 157 StartTime: bug.CrashTime.Format(time.RFC3339), 158 OutputFiles: outputFiles, 159 Description: bug.Title, 160 Status: "FAIL", 161 Waived: false, 162 Misc: &TestMisc{ 163 OriginURL: bug.Link, 164 ReportedBy: bug.CreditEmail, 165 UserSpaceArch: bug.UserSpaceArch, 166 CauseRevisionID: causeRevisionID, 167 }, 168 }, 169 } 170 } 171 return res 172 } 173 174 func normalizeRepo(repo string) string { 175 // Kcidb needs normalized repo addresses to match reports from different 176 // origins and with subscriptions. "https:" is always preferred over "git:" 177 // where available. Unfortunately we don't know where it's available 178 // and where it isn't. We know that "https:" is supported on kernel.org, 179 // and that's the main case we need to fix up. "https:" is always used 180 // for github.com and googlesource.com. 181 return strings.Replace(repo, "git://git.kernel.org", "https://git.kernel.org", -1) 182 } 183 184 func (c *Client) extID(id string) string { 185 return c.origin + ":" + id 186 }