aboutsummaryrefslogtreecommitdiffstats
path: root/syz-ci
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2025-11-24 08:04:20 +0100
committerDmitry Vyukov <dvyukov@google.com>2025-11-24 08:55:49 +0000
commita6deb8053825b4c7024c2b04f5d4f5a12ace1272 (patch)
treed694d4ae16e078d185ded1caa5819a6380b3c932 /syz-ci
parent080caeb9e30c884ab78354eae80c53cc47d8d49b (diff)
pkg/updater: factor out of syz-ci
Factor syzkaller updating functionality out of syz-ci so that it can be reused in other binaries. No functional changes intended.
Diffstat (limited to 'syz-ci')
-rw-r--r--syz-ci/manager.go9
-rw-r--r--syz-ci/syz-ci.go76
-rw-r--r--syz-ci/updater.go348
3 files changed, 73 insertions, 360 deletions
diff --git a/syz-ci/manager.go b/syz-ci/manager.go
index 4f68f2ca4..204bba875 100644
--- a/syz-ci/manager.go
+++ b/syz-ci/manager.go
@@ -33,6 +33,7 @@ import (
"github.com/google/syzkaller/pkg/mgrconfig"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/pkg/report"
+ "github.com/google/syzkaller/pkg/updater"
"github.com/google/syzkaller/pkg/vcs"
"github.com/google/syzkaller/prog"
_ "github.com/google/syzkaller/sys"
@@ -41,11 +42,11 @@ import (
)
// This is especially slightly longer than syzkaller rebuild period.
-// If we set kernelRebuildPeriod = syzkallerRebuildPeriod and both are changed
+// If we set kernelRebuildPeriod = updater.RebuildPeriod and both are changed
// during that period (or around that period), we can rebuild kernel, restart
// manager and then instantly shutdown everything for syzkaller update.
// Instead we rebuild syzkaller, restart and then rebuild kernel.
-const kernelRebuildPeriod = syzkallerRebuildPeriod + time.Hour
+const kernelRebuildPeriod = updater.RebuildPeriod + time.Hour
// List of required files in kernel build (contents of latest/current dirs).
var imageFiles = map[string]bool{
@@ -200,7 +201,7 @@ func (mgr *Manager) loop(ctx context.Context) {
benchUploadTime = time.Now().Add(benchUploadPeriod)
- ticker := time.NewTicker(buildRetryPeriod)
+ ticker := time.NewTicker(updater.BuildRetryPeriod)
defer ticker.Stop()
loop:
@@ -274,7 +275,7 @@ func (mgr *Manager) archiveCommit(commit string) {
func (mgr *Manager) pollAndBuild(ctx context.Context, lastCommit string, latestInfo *BuildInfo) (
string, *BuildInfo, time.Duration) {
- rebuildAfter := buildRetryPeriod
+ rebuildAfter := updater.BuildRetryPeriod
commit, err := mgr.repo.Poll(mgr.mgrcfg.Repo, mgr.mgrcfg.Branch)
if err != nil {
mgr.buildFailed = true
diff --git a/syz-ci/syz-ci.go b/syz-ci/syz-ci.go
index 7df27b683..ff1f90518 100644
--- a/syz-ci/syz-ci.go
+++ b/syz-ci/syz-ci.go
@@ -54,6 +54,7 @@ package main
import (
"context"
"encoding/json"
+ "errors"
"flag"
"fmt"
"net"
@@ -71,6 +72,7 @@ import (
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/mgrconfig"
"github.com/google/syzkaller/pkg/osutil"
+ "github.com/google/syzkaller/pkg/updater"
"github.com/google/syzkaller/pkg/vcs"
)
@@ -265,7 +267,6 @@ func main() {
serveHTTP(cfg)
- os.Unsetenv("GOPATH")
if cfg.Goroot != "" {
os.Setenv("GOROOT", cfg.Goroot)
os.Setenv("PATH", filepath.Join(cfg.Goroot, "bin")+
@@ -273,14 +274,29 @@ func main() {
}
updatePending := make(chan struct{})
- updater := NewSyzUpdater(cfg)
- updater.UpdateOnStart(*flagAutoUpdate, shutdownPending)
- if *flagAutoUpdate {
- go func() {
- updater.WaitForUpdate()
- close(updatePending)
- }()
+ updateTargets := make(map[updater.Target]bool)
+ for _, mgr := range cfg.Managers {
+ updateTargets[updater.Target{
+ OS: mgr.managercfg.TargetOS,
+ VMArch: mgr.managercfg.TargetVMArch,
+ Arch: mgr.managercfg.TargetArch,
+ }] = true
+ }
+ updater, err := updater.New(&updater.Config{
+ ExitOnUpdate: *flagExitOnUpgrade,
+ BuildSem: buildSem,
+ ReportBuildError: func(commit *vcs.Commit, compilerID string, buildErr error) {
+ uploadSyzkallerBuildError(cfg, commit, compilerID, buildErr)
+ },
+ SyzkallerRepo: cfg.SyzkallerRepo,
+ SyzkallerBranch: cfg.SyzkallerBranch,
+ SyzkallerDescriptions: cfg.SyzkallerDescriptions,
+ Targets: updateTargets,
+ })
+ if err != nil {
+ log.Fatal(err)
}
+ updater.UpdateOnStart(*flagAutoUpdate, updatePending, shutdownPending)
ctx, stop := context.WithCancel(context.Background())
var managers []*Manager
@@ -389,6 +405,50 @@ func serveHTTP(cfg *Config) {
}()
}
+func uploadSyzkallerBuildError(cfg *Config, commit *vcs.Commit, compilerID string, buildErr error) {
+ var output []byte
+ var verbose *osutil.VerboseError
+ title := buildErr.Error()
+ if errors.As(buildErr, &verbose) {
+ output = verbose.Output
+ }
+ title = "syzkaller: " + title
+ for _, mgrcfg := range cfg.Managers {
+ if cfg.DashboardAddr == "" || mgrcfg.DashboardClient == "" {
+ log.Logf(0, "not uploading build error for %v: no dashboard", mgrcfg.Name)
+ continue
+ }
+ dash, err := dashapi.New(mgrcfg.DashboardClient, cfg.DashboardAddr, mgrcfg.DashboardKey)
+ if err != nil {
+ log.Logf(0, "failed to report build error for %v: %v", mgrcfg.Name, err)
+ return
+ }
+ managercfg := mgrcfg.managercfg
+ req := &dashapi.BuildErrorReq{
+ Build: dashapi.Build{
+ Manager: managercfg.Name,
+ ID: commit.Hash,
+ OS: managercfg.TargetOS,
+ Arch: managercfg.TargetArch,
+ VMArch: managercfg.TargetVMArch,
+ SyzkallerCommit: commit.Hash,
+ SyzkallerCommitDate: commit.CommitDate,
+ CompilerID: compilerID,
+ KernelRepo: cfg.SyzkallerRepo,
+ KernelBranch: cfg.SyzkallerBranch,
+ },
+ Crash: dashapi.Crash{
+ Title: title,
+ Log: output,
+ },
+ }
+ if err := dash.ReportBuildError(req); err != nil {
+ // TODO: log ReportBuildError error to dashboard.
+ log.Logf(0, "failed to report build error for %v: %v", mgrcfg.Name, err)
+ }
+ }
+}
+
func loadConfig(filename string) (*Config, error) {
cfg := &Config{
SyzkallerRepo: "https://github.com/google/syzkaller.git",
diff --git a/syz-ci/updater.go b/syz-ci/updater.go
deleted file mode 100644
index 80e5b4db5..000000000
--- a/syz-ci/updater.go
+++ /dev/null
@@ -1,348 +0,0 @@
-// Copyright 2017 syzkaller project authors. All rights reserved.
-// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
-
-package main
-
-import (
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "strings"
- "syscall"
- "time"
-
- "github.com/google/syzkaller/dashboard/dashapi"
- "github.com/google/syzkaller/pkg/instance"
- "github.com/google/syzkaller/pkg/log"
- "github.com/google/syzkaller/pkg/osutil"
- "github.com/google/syzkaller/pkg/vcs"
- "github.com/google/syzkaller/prog"
- "github.com/google/syzkaller/sys/targets"
-)
-
-const (
- syzkallerRebuildPeriod = 12 * time.Hour
- buildRetryPeriod = 10 * time.Minute // used for both syzkaller and kernel
-)
-
-// SyzUpdater handles everything related to syzkaller updates.
-// As kernel builder, it maintains 2 builds:
-// - latest: latest known good syzkaller build
-// - current: currently used syzkaller build
-//
-// Additionally it updates and restarts the current executable as necessary.
-// Current executable is always built on the same revision as the rest of syzkaller binaries.
-type SyzUpdater struct {
- repo vcs.Repo
- exe string
- repoAddress string
- branch string
- descriptions string
- gopathDir string
- syzkallerDir string
- latestDir string
- currentDir string
- syzFiles map[string]bool
- targets map[string]bool
- dashboardAddr string
- compilerID string
- cfg *Config
-}
-
-func NewSyzUpdater(cfg *Config) *SyzUpdater {
- wd, err := os.Getwd()
- if err != nil {
- log.Fatalf("failed to get wd: %v", err)
- }
- bin := os.Args[0]
- if !filepath.IsAbs(bin) {
- bin = filepath.Join(wd, bin)
- }
- bin = filepath.Clean(bin)
- exe := filepath.Base(bin)
- if wd != filepath.Dir(bin) {
- log.Fatalf("%v executable must be in cwd (it will be overwritten on update)", exe)
- }
-
- gopath := filepath.Join(wd, "gopath")
- syzkallerDir := filepath.Join(gopath, "src", "github.com", "google", "syzkaller")
- osutil.MkdirAll(syzkallerDir)
-
- // List of required files in syzkaller build (contents of latest/current dirs).
- syzFiles := map[string]bool{
- "tag": true, // contains syzkaller repo git hash
- "bin/syz-ci": true, // these are just copied from syzkaller dir
- "bin/syz-manager": true,
- "sys/*/test/*": true,
- }
- targets := make(map[string]bool)
- for _, mgr := range cfg.Managers {
- mgrcfg := mgr.managercfg
- os, vmarch, arch := mgrcfg.TargetOS, mgrcfg.TargetVMArch, mgrcfg.TargetArch
- targets[os+"/"+vmarch+"/"+arch] = true
- syzFiles[fmt.Sprintf("bin/%v_%v/syz-execprog", os, vmarch)] = true
- if mgrcfg.SysTarget.ExecutorBin == "" {
- syzFiles[fmt.Sprintf("bin/%v_%v/syz-executor", os, arch)] = true
- }
- }
- compilerID, err := osutil.RunCmd(time.Minute, "", "go", "version")
- if err != nil {
- log.Fatalf("%v", err)
- }
- return &SyzUpdater{
- repo: vcs.NewSyzkallerRepo(syzkallerDir),
- exe: exe,
- repoAddress: cfg.SyzkallerRepo,
- branch: cfg.SyzkallerBranch,
- descriptions: cfg.SyzkallerDescriptions,
- gopathDir: gopath,
- syzkallerDir: syzkallerDir,
- latestDir: filepath.Join("syzkaller", "latest"),
- currentDir: filepath.Join("syzkaller", "current"),
- syzFiles: syzFiles,
- targets: targets,
- dashboardAddr: cfg.DashboardAddr,
- compilerID: strings.TrimSpace(string(compilerID)),
- cfg: cfg,
- }
-}
-
-// UpdateOnStart does 3 things:
-// - ensures that the current executable is fresh
-// - ensures that we have a working syzkaller build in current
-func (upd *SyzUpdater) UpdateOnStart(autoupdate bool, shutdown chan struct{}) {
- os.RemoveAll(upd.currentDir)
- latestTag := upd.checkLatest()
- if latestTag != "" {
- var exeMod time.Time
- if st, err := os.Stat(upd.exe); err == nil {
- exeMod = st.ModTime()
- }
- uptodate := prog.GitRevisionBase == latestTag && time.Since(exeMod) < time.Minute
- if uptodate || !autoupdate {
- if uptodate {
- // Have a fresh up-to-date build, probably just restarted.
- log.Logf(0, "current executable is up-to-date (%v)", latestTag)
- } else {
- log.Logf(0, "autoupdate is turned off, using latest build %v", latestTag)
- }
- if err := osutil.LinkFiles(upd.latestDir, upd.currentDir, upd.syzFiles); err != nil {
- log.Fatal(err)
- }
- return
- }
- }
- log.Logf(0, "current executable is on %v", prog.GitRevision)
- log.Logf(0, "latest syzkaller build is on %v", latestTag)
-
- // No syzkaller build or executable is stale.
- lastCommit := prog.GitRevisionBase
- if lastCommit != latestTag {
- // Latest build and syz-ci are inconsistent. Rebuild everything.
- lastCommit = ""
- latestTag = ""
- }
- for {
- lastCommit = upd.pollAndBuild(lastCommit)
- latestTag := upd.checkLatest()
- if latestTag != "" {
- // The build was successful or we had the latest build from previous runs.
- // Either way, use the latest build.
- log.Logf(0, "using syzkaller built on %v", latestTag)
- if err := osutil.LinkFiles(upd.latestDir, upd.currentDir, upd.syzFiles); err != nil {
- log.Fatal(err)
- }
- if autoupdate && prog.GitRevisionBase != latestTag {
- upd.UpdateAndRestart()
- }
- return
- }
-
- // No good build at all, try again later.
- log.Logf(0, "retrying in %v", buildRetryPeriod)
- select {
- case <-time.After(buildRetryPeriod):
- case <-shutdown:
- os.Exit(0)
- }
- }
-}
-
-// WaitForUpdate polls and rebuilds syzkaller.
-// Returns when we have a new good build in latest.
-func (upd *SyzUpdater) WaitForUpdate() {
- time.Sleep(syzkallerRebuildPeriod)
- latestTag := upd.checkLatest()
- lastCommit := latestTag
- for {
- lastCommit = upd.pollAndBuild(lastCommit)
- if latestTag != upd.checkLatest() {
- break
- }
- time.Sleep(buildRetryPeriod)
- }
- log.Logf(0, "syzkaller: update available, restarting")
-}
-
-// UpdateAndRestart updates and restarts the current executable.
-// Does not return.
-func (upd *SyzUpdater) UpdateAndRestart() {
- log.Logf(0, "restarting executable for update")
- latestBin := filepath.Join(upd.latestDir, "bin", upd.exe)
- if err := osutil.CopyFile(latestBin, upd.exe); err != nil {
- log.Fatal(err)
- }
- if *flagExitOnUpgrade {
- log.Logf(0, "exiting, please restart syz-ci to run the new version")
- os.Exit(0)
- }
- if err := syscall.Exec(upd.exe, os.Args, os.Environ()); err != nil {
- log.Fatal(err)
- }
- log.Fatalf("not reachable")
-}
-
-func (upd *SyzUpdater) pollAndBuild(lastCommit string) string {
- commit, err := upd.repo.Poll(upd.repoAddress, upd.branch)
- if err != nil {
- log.Logf(0, "syzkaller: failed to poll: %v", err)
- return lastCommit
- }
- log.Logf(0, "syzkaller: poll: %v (%v)", commit.Hash, commit.Title)
- if lastCommit == commit.Hash {
- return lastCommit
- }
- log.Logf(0, "syzkaller: building ...")
- if err := upd.build(commit); err != nil {
- log.Logf(0, "syzkaller: %v", err)
- upd.uploadBuildError(commit, err)
- }
- return commit.Hash
-}
-
-// nolint: goconst // "GOPATH=" looks good here, ignore
-func (upd *SyzUpdater) build(commit *vcs.Commit) error {
- // syzkaller testing may be slowed down by concurrent kernel builds too much
- // and cause timeout failures, so we serialize it with other builds:
- // https://groups.google.com/forum/#!msg/syzkaller-openbsd-bugs/o-G3vEsyQp4/f_nFpoNKBQAJ
- buildSem.Wait()
- defer buildSem.Signal()
-
- if upd.descriptions != "" {
- files, err := os.ReadDir(upd.descriptions)
- if err != nil {
- return fmt.Errorf("failed to read descriptions dir: %w", err)
- }
- for _, f := range files {
- src := filepath.Join(upd.descriptions, f.Name())
- dst := ""
- switch filepath.Ext(src) {
- case ".txt", ".const":
- dst = filepath.Join(upd.syzkallerDir, "sys", targets.Linux, f.Name())
- case ".test":
- dst = filepath.Join(upd.syzkallerDir, "sys", targets.Linux, "test", f.Name())
- case ".h":
- dst = filepath.Join(upd.syzkallerDir, "executor", f.Name())
- default:
- continue
- }
- if err := osutil.CopyFile(src, dst); err != nil {
- return err
- }
- }
- }
- // This will also generate descriptions and should go before the 'go test' below.
- cmd := osutil.Command(instance.MakeBin, "host", "ci")
- cmd.Dir = upd.syzkallerDir
- cmd.Env = append([]string{"GOPATH=" + upd.gopathDir}, os.Environ()...)
- if _, err := osutil.Run(time.Hour, cmd); err != nil {
- return fmt.Errorf("make host failed: %w", err)
- }
- for target := range upd.targets {
- parts := strings.Split(target, "/")
- cmd = osutil.Command(instance.MakeBin, "target")
- cmd.Dir = upd.syzkallerDir
- cmd.Env = append([]string{}, os.Environ()...)
- cmd.Env = append(cmd.Env,
- "GOPATH="+upd.gopathDir,
- "TARGETOS="+parts[0],
- "TARGETVMARCH="+parts[1],
- "TARGETARCH="+parts[2],
- )
- if _, err := osutil.Run(time.Hour, cmd); err != nil {
- return fmt.Errorf("make target failed: %w", err)
- }
- }
- cmd = osutil.Command("go", "test", "-short", "./...")
- cmd.Dir = upd.syzkallerDir
- cmd.Env = append([]string{
- "GOPATH=" + upd.gopathDir,
- "SYZ_DISABLE_SANDBOXING=yes",
- }, os.Environ()...)
- if _, err := osutil.Run(time.Hour, cmd); err != nil {
- return fmt.Errorf("testing failed: %w", err)
- }
- tagFile := filepath.Join(upd.syzkallerDir, "tag")
- if err := osutil.WriteFile(tagFile, []byte(commit.Hash)); err != nil {
- return fmt.Errorf("failed to write tag file: %w", err)
- }
- if err := osutil.CopyFiles(upd.syzkallerDir, upd.latestDir, upd.syzFiles); err != nil {
- return fmt.Errorf("failed to copy syzkaller: %w", err)
- }
- return nil
-}
-
-func (upd *SyzUpdater) uploadBuildError(commit *vcs.Commit, buildErr error) {
- var output []byte
- var verbose *osutil.VerboseError
- title := buildErr.Error()
- if errors.As(buildErr, &verbose) {
- output = verbose.Output
- }
- title = "syzkaller: " + title
- for _, mgrcfg := range upd.cfg.Managers {
- if upd.dashboardAddr == "" || mgrcfg.DashboardClient == "" {
- log.Logf(0, "not uploading build error for %v: no dashboard", mgrcfg.Name)
- continue
- }
- dash, err := dashapi.New(mgrcfg.DashboardClient, upd.dashboardAddr, mgrcfg.DashboardKey)
- if err != nil {
- log.Logf(0, "failed to report build error for %v: %v", mgrcfg.Name, err)
- return
- }
- managercfg := mgrcfg.managercfg
- req := &dashapi.BuildErrorReq{
- Build: dashapi.Build{
- Manager: managercfg.Name,
- ID: commit.Hash,
- OS: managercfg.TargetOS,
- Arch: managercfg.TargetArch,
- VMArch: managercfg.TargetVMArch,
- SyzkallerCommit: commit.Hash,
- SyzkallerCommitDate: commit.CommitDate,
- CompilerID: upd.compilerID,
- KernelRepo: upd.repoAddress,
- KernelBranch: upd.branch,
- },
- Crash: dashapi.Crash{
- Title: title,
- Log: output,
- },
- }
- if err := dash.ReportBuildError(req); err != nil {
- // TODO: log ReportBuildError error to dashboard.
- log.Logf(0, "failed to report build error for %v: %v", mgrcfg.Name, err)
- }
- }
-}
-
-// checkLatest returns tag of the latest build,
-// or an empty string if latest build is missing/broken.
-func (upd *SyzUpdater) checkLatest() string {
- if !osutil.FilesExist(upd.latestDir, upd.syzFiles) {
- return ""
- }
- tag, _ := os.ReadFile(filepath.Join(upd.latestDir, "tag"))
- return string(tag)
-}