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  }