github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-ci/syz-ci.go (about)

     1  // Copyright 2017 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  // syz-ci is a continuous fuzzing system for syzkaller.
     5  // It runs several syz-manager's, polls and rebuilds images for managers
     6  // and polls and rebuilds syzkaller binaries.
     7  // For usage instructions see: docs/ci.md.
     8  package main
     9  
    10  // Implementation details:
    11  //
    12  // 2 main components:
    13  //  - SyzUpdater: handles syzkaller updates
    14  //  - Manager: handles kernel build and syz-manager process (one per manager)
    15  // Both operate in a similar way and keep 2 builds:
    16  //  - latest: latest known good build (i.e. we tested it)
    17  //    preserved across restarts/reboots, i.e. we can start fuzzing even when
    18  //    current syzkaller/kernel git head is broken, or git is down, or anything else
    19  //  - current: currently used build (a copy of one of the latest builds)
    20  // Other important points:
    21  //  - syz-ci is always built on the same revision as the rest of syzkaller binaries,
    22  //    this allows us to handle e.g. changes in manager config format.
    23  //  - consequently, syzkaller binaries are never updated on-the-fly,
    24  //    instead we re-exec and then update
    25  //  - we understand when the latest build is fresh even after reboot,
    26  //    i.e. we store enough information to identify it (git hash, compiler identity, etc),
    27  //    so we don't rebuild unnecessary (kernel builds take time)
    28  //  - we generally avoid crashing the process and handle all errors gracefully
    29  //    (this is a continuous system), except for some severe/user errors during start
    30  //    (e.g. bad config file, or can't create necessary dirs)
    31  //
    32  // Directory/file structure:
    33  // syz-ci			: current executable
    34  // syzkaller/
    35  //	latest/			: latest good syzkaller build
    36  //	current/		: syzkaller build currently in use
    37  // managers/
    38  //	manager1/		: one dir per manager
    39  //		kernel/		: kernel checkout
    40  //		workdir/	: manager workdir (never deleted)
    41  //		latest/		: latest good kernel image build
    42  //		current/	: kernel image currently in use
    43  // jobs/
    44  //	linux/			: one dir per target OS
    45  //		kernel/		: kernel checkout
    46  //		image/		: currently used image
    47  //		workdir/	: some temp files
    48  //
    49  // Current executable, syzkaller and kernel builds are marked with tag files.
    50  // Tag files uniquely identify the build (git hash, compiler identity, kernel config, etc).
    51  // For tag files both contents and modification time are important,
    52  // modification time allows us to understand if we need to rebuild after a restart.
    53  
    54  import (
    55  	"context"
    56  	"encoding/json"
    57  	"errors"
    58  	"flag"
    59  	"fmt"
    60  	"net"
    61  	"net/http"
    62  	_ "net/http/pprof"
    63  	"os"
    64  	"path/filepath"
    65  	"strings"
    66  	"sync"
    67  	"time"
    68  
    69  	"github.com/google/syzkaller/dashboard/dashapi"
    70  	"github.com/google/syzkaller/pkg/asset"
    71  	"github.com/google/syzkaller/pkg/config"
    72  	"github.com/google/syzkaller/pkg/log"
    73  	"github.com/google/syzkaller/pkg/mgrconfig"
    74  	"github.com/google/syzkaller/pkg/osutil"
    75  	"github.com/google/syzkaller/pkg/updater"
    76  	"github.com/google/syzkaller/pkg/vcs"
    77  )
    78  
    79  var (
    80  	flagConfig     = flag.String("config", "", "config file")
    81  	flagAutoUpdate = flag.Bool("autoupdate", true, "auto-update the binary (for testing)")
    82  	flagManagers   = flag.Bool("managers", true, "start managers (for testing)")
    83  	flagDebug      = flag.Bool("debug", false, "debug mode (for testing)")
    84  	// nolint: lll
    85  	flagExitOnUpgrade = flag.Bool("exit-on-upgrade", false, "exit after a syz-ci upgrade is applied; otherwise syz-ci restarts")
    86  )
    87  
    88  type Config struct {
    89  	Name string `json:"name"`
    90  	HTTP string `json:"http"`
    91  	// If manager http address is not specified, give it an address starting from this port. Optional.
    92  	// This is also used to auto-assign ports for test instances.
    93  	ManagerPort int `json:"manager_port_start"`
    94  	// If manager rpc address is not specified, give it addresses starting from this port. By default 30000.
    95  	// This is also used to auto-assign ports for test instances.
    96  	RPCPort         int    `json:"rpc_port_start"`
    97  	DashboardAddr   string `json:"dashboard_addr"`   // Optional.
    98  	DashboardClient string `json:"dashboard_client"` // Optional.
    99  	DashboardKey    string `json:"dashboard_key"`    // Optional.
   100  	HubAddr         string `json:"hub_addr"`         // Optional.
   101  	HubKey          string `json:"hub_key"`          // Optional.
   102  	Goroot          string `json:"goroot"`           // Go 1.8+ toolchain dir.
   103  	SyzkallerRepo   string `json:"syzkaller_repo"`
   104  	SyzkallerBranch string `json:"syzkaller_branch"` // Defaults to "master".
   105  	// Dir with additional syscall descriptions.
   106  	// - *.txt and *.const files are copied to syzkaller/sys/linux/
   107  	// - *.test files are copied to syzkaller/sys/linux/test/
   108  	// - *.h files are copied to syzkaller/executor/
   109  	SyzkallerDescriptions string `json:"syzkaller_descriptions"`
   110  	// Path to upload coverage reports from managers (optional).
   111  	// Supported protocols: GCS (gs://) and HTTP PUT (http:// or https://).
   112  	CoverUploadPath string `json:"cover_upload_path"`
   113  	// Path to upload managers syz programs and their coverage in jsonl (optional).
   114  	CoverProgramsPath string `json:"cover_programs_path"`
   115  	// Path to upload json coverage reports from managers (optional).
   116  	CoverPipelinePath string `json:"cover_pipeline_path"`
   117  	// Path to upload corpus.db from managers (optional).
   118  	// Supported protocols: GCS (gs://) and HTTP PUT (http:// or https://).
   119  	CorpusUploadPath string `json:"corpus_upload_path"`
   120  	// Make files uploaded via CoverUploadPath, CorpusUploadPath and CoverProgramsPath public.
   121  	PublishGCS bool `json:"publish_gcs"`
   122  	// Path to upload bench data from instances (optional).
   123  	// Supported protocols: GCS (gs://) and HTTP PUT (http:// or https://).
   124  	BenchUploadPath string `json:"bench_upload_path"`
   125  	// BinDir must point to a dir that contains compilers required to build
   126  	// older versions of the kernel. For linux, it needs to include several
   127  	// compiler versions.
   128  	BisectBinDir string `json:"bisect_bin_dir"`
   129  	// Keys of BisectIgnore are full commit hashes that should never be reported
   130  	// in bisection results.
   131  	// Values of the map are ignored and can e.g. serve as comments.
   132  	BisectIgnore map[string]string `json:"bisect_ignore"`
   133  	// Extra commits to cherry-pick to older kernel revisions.
   134  	// The list is concatenated with the similar parameter from ManagerConfig.
   135  	BisectBackports []vcs.BackportCommit `json:"bisect_backports"`
   136  	Ccache          string               `json:"ccache"`
   137  	// BuildCPUs defines the maximum number of parallel kernel build threads.
   138  	BuildCPUs int              `json:"build_cpus"`
   139  	Managers  []*ManagerConfig `json:"managers"`
   140  	// Poll period for jobs in seconds (optional, defaults to 10 seconds)
   141  	JobPollPeriod int `json:"job_poll_period"`
   142  	// Set up a second (parallel) job processor to speed up processing.
   143  	// For now, this second job processor only handles patch testing requests.
   144  	ParallelJobs bool `json:"parallel_jobs"`
   145  	// Poll period for commits in seconds (optional, defaults to 3600 seconds)
   146  	CommitPollPeriod int `json:"commit_poll_period"`
   147  	// Asset Storage config.
   148  	AssetStorage *asset.Config `json:"asset_storage"`
   149  	// Per-vm type JSON diffs that will be applied to every instace of the
   150  	// corresponding VM type.
   151  	PatchVMConfigs map[string]json.RawMessage `json:"patch_vm_configs"`
   152  	// Some commits don't live long.
   153  	// Push all commits used in kernel builds to this git repo URL.
   154  	// The archive is later used by coverage merger.
   155  	GitArchive string `json:"git_archive"`
   156  }
   157  
   158  type ManagerConfig struct {
   159  	// If Name is specified, syz-manager name is set to Config.Name-ManagerConfig.Name.
   160  	// This is old naming scheme, it does not allow to move managers between ci instances.
   161  	// For new naming scheme set ManagerConfig.ManagerConfig.Name instead and leave this field empty.
   162  	// This allows to move managers as their name does not depend on cfg.Name.
   163  	// Generally, if you have:
   164  	// {
   165  	//   "name": "ci",
   166  	//   "managers": [
   167  	//     {
   168  	//       "name": "foo",
   169  	//       ...
   170  	//     }
   171  	//   ]
   172  	// }
   173  	// you want to change it to:
   174  	// {
   175  	//   "name": "ci",
   176  	//   "managers": [
   177  	//     {
   178  	//       ...
   179  	//       "manager_config": {
   180  	//         "name": "ci-foo"
   181  	//       }
   182  	//     }
   183  	//   ]
   184  	// }
   185  	// and rename managers/foo to managers/ci-foo. Then this instance can be moved
   186  	// to another ci along with managers/ci-foo dir.
   187  	Name            string `json:"name"`
   188  	Disabled        string `json:"disabled"`         // If not empty, don't build/start this manager.
   189  	DashboardClient string `json:"dashboard_client"` // Optional.
   190  	DashboardKey    string `json:"dashboard_key"`    // Optional.
   191  	Repo            string `json:"repo"`
   192  	// Short name of the repo (e.g. "linux-next"), used only for reporting.
   193  	RepoAlias string `json:"repo_alias"`
   194  	Branch    string `json:"branch"` // Defaults to "master".
   195  	// Currently either 'gcc' or 'clang'. Note that pkg/bisect requires
   196  	// explicit plumbing for every os/compiler combination.
   197  	CompilerType string `json:"compiler_type"` // Defaults to "gcc"
   198  	Compiler     string `json:"compiler"`
   199  	Make         string `json:"make"`
   200  	Linker       string `json:"linker"`
   201  	Ccache       string `json:"ccache"`
   202  	Userspace    string `json:"userspace"`
   203  	KernelConfig string `json:"kernel_config"`
   204  	// KernelSrcSuffix adds a suffix to the kernel_src manager config. This is needed for cases where
   205  	// the kernel source root as reported in the coverage UI is a subdirectory of the VCS root.
   206  	KernelSrcSuffix string `json:"kernel_src_suffix"`
   207  	// Build-type-specific parameters.
   208  	// Parameters for concrete types are in Config type in pkg/build/TYPE.go, e.g. pkg/build/android.go.
   209  	Build json.RawMessage `json:"build"`
   210  	// Baseline config for bisection, see pkg/bisect.KernelConfig.BaselineConfig.
   211  	// If not specified, syz-ci generates a `-base.config` path counterpart for `kernel_config` and,
   212  	// if it exists, uses it as default.
   213  	KernelBaselineConfig string `json:"kernel_baseline_config"`
   214  	// File with kernel cmdline values (optional).
   215  	KernelCmdline string `json:"kernel_cmdline"`
   216  	// File with sysctl values (e.g. output of sysctl -a, optional).
   217  	KernelSysctl string      `json:"kernel_sysctl"`
   218  	Jobs         ManagerJobs `json:"jobs"`
   219  	// Extra commits to cherry pick to older kernel revisions.
   220  	BisectBackports []vcs.BackportCommit `json:"bisect_backports"`
   221  	// Base syz-manager config for the instance.
   222  	ManagerConfig json.RawMessage `json:"manager_config"`
   223  	// By default we want to archive git commits.
   224  	// This opt-out is needed for *BSD systems.
   225  	DisableGitArchive bool `json:"disable_git_archive"`
   226  	// If the kernel's commit is older than MaxKernelLagDays days,
   227  	// fuzzing won't be started on this instance.
   228  	// By default it's 30 days.
   229  	MaxKernelLagDays int `json:"max_kernel_lag_days"`
   230  	managercfg       *mgrconfig.Config
   231  
   232  	// Auto-assigned ports used by test instances.
   233  	testRPCPort int
   234  }
   235  
   236  type ManagerJobs struct {
   237  	TestPatches bool `json:"test_patches"` // enable patch testing jobs
   238  	PollCommits bool `json:"poll_commits"` // poll info about fix commits
   239  	BisectCause bool `json:"bisect_cause"` // do cause bisection
   240  	BisectFix   bool `json:"bisect_fix"`   // do fix bisection
   241  }
   242  
   243  func (m *ManagerJobs) AnyEnabled() bool {
   244  	return m.TestPatches || m.PollCommits || m.BisectCause || m.BisectFix
   245  }
   246  
   247  func (m *ManagerJobs) Filter(filter *ManagerJobs) *ManagerJobs {
   248  	return &ManagerJobs{
   249  		TestPatches: m.TestPatches && filter.TestPatches,
   250  		PollCommits: m.PollCommits && filter.PollCommits,
   251  		BisectCause: m.BisectCause && filter.BisectCause,
   252  		BisectFix:   m.BisectFix && filter.BisectFix,
   253  	}
   254  }
   255  
   256  func main() {
   257  	flag.Parse()
   258  	log.EnableLogCaching(1000, 1<<20)
   259  	cfg, err := loadConfig(*flagConfig)
   260  	if err != nil {
   261  		log.Fatalf("failed to load config: %v", err)
   262  	}
   263  	log.SetName(cfg.Name)
   264  
   265  	shutdownPending := make(chan struct{})
   266  	osutil.HandleInterrupts(shutdownPending)
   267  
   268  	serveHTTP(cfg)
   269  
   270  	if cfg.Goroot != "" {
   271  		os.Setenv("GOROOT", cfg.Goroot)
   272  		os.Setenv("PATH", filepath.Join(cfg.Goroot, "bin")+
   273  			string(filepath.ListSeparator)+os.Getenv("PATH"))
   274  	}
   275  
   276  	updatePending := make(chan struct{})
   277  	updateTargets := make(map[updater.Target]bool)
   278  	for _, mgr := range cfg.Managers {
   279  		updateTargets[updater.Target{
   280  			OS:     mgr.managercfg.TargetOS,
   281  			VMArch: mgr.managercfg.TargetVMArch,
   282  			Arch:   mgr.managercfg.TargetArch,
   283  		}] = true
   284  	}
   285  	updater, err := updater.New(&updater.Config{
   286  		ExitOnUpdate: *flagExitOnUpgrade,
   287  		BuildSem:     buildSem,
   288  		ReportBuildError: func(commit *vcs.Commit, compilerID string, buildErr error) {
   289  			uploadSyzkallerBuildError(cfg, commit, compilerID, buildErr)
   290  		},
   291  		SyzkallerRepo:         cfg.SyzkallerRepo,
   292  		SyzkallerBranch:       cfg.SyzkallerBranch,
   293  		SyzkallerDescriptions: cfg.SyzkallerDescriptions,
   294  		Targets:               updateTargets,
   295  	})
   296  	if err != nil {
   297  		log.Fatal(err)
   298  	}
   299  	updater.UpdateOnStart(*flagAutoUpdate, updatePending, shutdownPending)
   300  
   301  	ctx, stop := context.WithCancel(context.Background())
   302  	var managers []*Manager
   303  	for _, mgrcfg := range cfg.Managers {
   304  		mgr, err := createManager(cfg, mgrcfg, *flagDebug)
   305  		if err != nil {
   306  			log.Errorf("failed to create manager %v: %v", mgrcfg.Name, err)
   307  			continue
   308  		}
   309  		managers = append(managers, mgr)
   310  	}
   311  	if len(managers) == 0 {
   312  		log.Fatalf("failed to create all managers")
   313  	}
   314  	var wg sync.WaitGroup
   315  	if *flagManagers {
   316  		for _, mgr := range managers {
   317  			wg.Add(1)
   318  			go func() {
   319  				defer wg.Done()
   320  				mgr.loop(ctx)
   321  			}()
   322  		}
   323  	}
   324  
   325  	ctxJobs, stopJobs := context.WithCancel(ctx)
   326  	wgJobs := sync.WaitGroup{}
   327  	if cfg.DashboardAddr != "" {
   328  		jm, err := newJobManager(cfg, managers, shutdownPending)
   329  		if err != nil {
   330  			log.Fatalf("failed to create dashapi connection %v", err)
   331  		}
   332  		jm.startLoop(ctxJobs, &wgJobs)
   333  	}
   334  
   335  	// For testing. Racy. Use with care.
   336  	http.HandleFunc("/upload_cover", func(w http.ResponseWriter, r *http.Request) {
   337  		for _, mgr := range managers {
   338  			if err := mgr.uploadCoverReport(ctx); err != nil {
   339  				fmt.Fprintf(w, "failed for %v: %v <br>\n", mgr.name, err)
   340  				return
   341  			}
   342  			fmt.Fprintf(w, "upload cover for %v <br>\n", mgr.name)
   343  		}
   344  	})
   345  
   346  	wg.Add(1)
   347  	go deprecateAssets(ctx, cfg, &wg)
   348  
   349  	select {
   350  	case <-shutdownPending:
   351  	case <-updatePending:
   352  	}
   353  	stopJobs()
   354  	wgJobs.Wait()
   355  	stop()
   356  	wg.Wait()
   357  
   358  	select {
   359  	case <-shutdownPending:
   360  	default:
   361  		updater.UpdateAndRestart()
   362  	}
   363  }
   364  
   365  func deprecateAssets(ctx context.Context, cfg *Config, wg *sync.WaitGroup) {
   366  	defer wg.Done()
   367  	if cfg.DashboardAddr == "" || cfg.AssetStorage.IsEmpty() ||
   368  		!cfg.AssetStorage.DoDeprecation {
   369  		return
   370  	}
   371  	dash, err := dashapi.New(cfg.DashboardClient, cfg.DashboardAddr, cfg.DashboardKey)
   372  	if err != nil {
   373  		log.Fatalf("failed to create dashapi during asset deprecation: %v", err)
   374  		return
   375  	}
   376  	storage, err := asset.StorageFromConfig(cfg.AssetStorage, dash)
   377  	if err != nil {
   378  		log.Errorf("failed to create asset storage during asset deprecation: %v", err)
   379  		return
   380  	}
   381  loop:
   382  	for {
   383  		const sleepDuration = 6 * time.Hour
   384  		select {
   385  		case <-ctx.Done():
   386  			break loop
   387  		case <-time.After(sleepDuration):
   388  		}
   389  		log.Logf(1, "start asset deprecation")
   390  		stats, err := storage.DeprecateAssets()
   391  		if err != nil {
   392  			log.Errorf("asset deprecation failed: %v", err)
   393  		}
   394  		log.Logf(0, "asset deprecation: needed=%d, existing=%d, deleted=%d",
   395  			stats.Needed, stats.Existing, stats.Deleted)
   396  	}
   397  }
   398  
   399  func serveHTTP(cfg *Config) {
   400  	ln, err := net.Listen("tcp4", cfg.HTTP)
   401  	if err != nil {
   402  		log.Fatalf("failed to listen on %v: %v", cfg.HTTP, err)
   403  	}
   404  	log.Logf(0, "serving http on http://%v", ln.Addr())
   405  	go func() {
   406  		err := http.Serve(ln, nil)
   407  		log.Fatalf("failed to serve http: %v", err)
   408  	}()
   409  }
   410  
   411  func uploadSyzkallerBuildError(cfg *Config, commit *vcs.Commit, compilerID string, buildErr error) {
   412  	var output []byte
   413  	var verbose *osutil.VerboseError
   414  	title := buildErr.Error()
   415  	if errors.As(buildErr, &verbose) {
   416  		output = verbose.Output
   417  	}
   418  	title = "syzkaller: " + title
   419  	for _, mgrcfg := range cfg.Managers {
   420  		if cfg.DashboardAddr == "" || mgrcfg.DashboardClient == "" {
   421  			log.Logf(0, "not uploading build error for %v: no dashboard", mgrcfg.Name)
   422  			continue
   423  		}
   424  		dash, err := dashapi.New(mgrcfg.DashboardClient, cfg.DashboardAddr, mgrcfg.DashboardKey)
   425  		if err != nil {
   426  			log.Logf(0, "failed to report build error for %v: %v", mgrcfg.Name, err)
   427  			return
   428  		}
   429  		managercfg := mgrcfg.managercfg
   430  		req := &dashapi.BuildErrorReq{
   431  			Build: dashapi.Build{
   432  				Manager:             managercfg.Name,
   433  				ID:                  commit.Hash,
   434  				OS:                  managercfg.TargetOS,
   435  				Arch:                managercfg.TargetArch,
   436  				VMArch:              managercfg.TargetVMArch,
   437  				SyzkallerCommit:     commit.Hash,
   438  				SyzkallerCommitDate: commit.CommitDate,
   439  				CompilerID:          compilerID,
   440  				KernelRepo:          cfg.SyzkallerRepo,
   441  				KernelBranch:        cfg.SyzkallerBranch,
   442  			},
   443  			Crash: dashapi.Crash{
   444  				Title: title,
   445  				Log:   output,
   446  			},
   447  		}
   448  		if err := dash.ReportBuildError(req); err != nil {
   449  			// TODO: log ReportBuildError error to dashboard.
   450  			log.Logf(0, "failed to report build error for %v: %v", mgrcfg.Name, err)
   451  		}
   452  	}
   453  }
   454  
   455  func loadConfig(filename string) (*Config, error) {
   456  	cfg := &Config{
   457  		SyzkallerRepo:    "https://github.com/google/syzkaller.git",
   458  		SyzkallerBranch:  "master",
   459  		ManagerPort:      10000,
   460  		RPCPort:          30000,
   461  		Goroot:           os.Getenv("GOROOT"),
   462  		JobPollPeriod:    10,
   463  		CommitPollPeriod: 3600,
   464  	}
   465  	if err := config.LoadFile(filename, cfg); err != nil {
   466  		return nil, err
   467  	}
   468  	if cfg.Name == "" {
   469  		return nil, fmt.Errorf("param 'name' is empty")
   470  	}
   471  	if cfg.HTTP == "" {
   472  		return nil, fmt.Errorf("param 'http' is empty")
   473  	}
   474  	cfg.Goroot = osutil.Abs(cfg.Goroot)
   475  	cfg.SyzkallerDescriptions = osutil.Abs(cfg.SyzkallerDescriptions)
   476  	cfg.BisectBinDir = osutil.Abs(cfg.BisectBinDir)
   477  	cfg.Ccache = osutil.Abs(cfg.Ccache)
   478  	var managers []*ManagerConfig
   479  	for _, mgr := range cfg.Managers {
   480  		if mgr.Disabled == "" {
   481  			managers = append(managers, mgr)
   482  		}
   483  		if err := loadManagerConfig(cfg, mgr); err != nil {
   484  			return nil, err
   485  		}
   486  	}
   487  	cfg.Managers = managers
   488  	if len(cfg.Managers) == 0 {
   489  		return nil, fmt.Errorf("no managers specified")
   490  	}
   491  	if cfg.AssetStorage != nil {
   492  		if err := cfg.AssetStorage.Validate(); err != nil {
   493  			return nil, fmt.Errorf("asset storage config error: %w", err)
   494  		}
   495  	}
   496  	return cfg, nil
   497  }
   498  
   499  func loadManagerConfig(cfg *Config, mgr *ManagerConfig) error {
   500  	managercfg, err := mgrconfig.LoadPartialData(mgr.ManagerConfig)
   501  	if err != nil {
   502  		return fmt.Errorf("manager config: %w", err)
   503  	}
   504  	if managercfg.Name != "" && mgr.Name != "" {
   505  		return fmt.Errorf("both managercfg.Name=%q and mgr.Name=%q are specified", managercfg.Name, mgr.Name)
   506  	}
   507  	if managercfg.Name == "" && mgr.Name == "" {
   508  		return fmt.Errorf("no managercfg.Name nor mgr.Name are specified")
   509  	}
   510  	if managercfg.Name != "" {
   511  		mgr.Name = managercfg.Name
   512  	} else {
   513  		managercfg.Name = cfg.Name + "-" + mgr.Name
   514  	}
   515  	if mgr.CompilerType == "" {
   516  		mgr.CompilerType = "gcc"
   517  	}
   518  	if mgr.Branch == "" {
   519  		mgr.Branch = "master"
   520  	}
   521  	mgr.managercfg = managercfg
   522  	managercfg.Syzkaller = filepath.FromSlash("syzkaller/current")
   523  	if managercfg.HTTP == "" {
   524  		managercfg.HTTP = fmt.Sprintf(":%v", cfg.ManagerPort)
   525  		cfg.ManagerPort++
   526  	}
   527  	if managercfg.RPC == ":0" {
   528  		managercfg.RPC = fmt.Sprintf(":%v", cfg.RPCPort)
   529  		cfg.RPCPort++
   530  	}
   531  	mgr.testRPCPort = cfg.RPCPort
   532  	cfg.RPCPort++
   533  	// Note: we don't change Compiler/Ccache because it may be just "gcc" referring
   534  	// to the system binary, or pkg/build/netbsd.go uses "g++" and "clang++" as special marks.
   535  	mgr.Userspace = osutil.Abs(mgr.Userspace)
   536  	mgr.KernelConfig = osutil.Abs(mgr.KernelConfig)
   537  	mgr.KernelBaselineConfig = osutil.Abs(mgr.KernelBaselineConfig)
   538  	mgr.KernelCmdline = osutil.Abs(mgr.KernelCmdline)
   539  	mgr.KernelSysctl = osutil.Abs(mgr.KernelSysctl)
   540  	if mgr.KernelConfig != "" && mgr.KernelBaselineConfig == "" {
   541  		mgr.KernelBaselineConfig = inferBaselineConfig(mgr.KernelConfig)
   542  	}
   543  	if mgr.MaxKernelLagDays == 0 {
   544  		mgr.MaxKernelLagDays = 30
   545  	}
   546  	if err := mgr.validate(cfg); err != nil {
   547  		return err
   548  	}
   549  
   550  	if cfg.PatchVMConfigs[managercfg.Type] != nil {
   551  		managercfg.VM, err = config.MergeJSONs(managercfg.VM, cfg.PatchVMConfigs[managercfg.Type])
   552  		if err != nil {
   553  			return fmt.Errorf("failed to patch manager %v's VM: %w", mgr.Name, err)
   554  		}
   555  	}
   556  	return nil
   557  }
   558  
   559  func inferBaselineConfig(kernelConfig string) string {
   560  	suffixPos := strings.LastIndex(kernelConfig, ".config")
   561  	if suffixPos < 0 {
   562  		return ""
   563  	}
   564  	candidate := kernelConfig[:suffixPos] + "-base.config"
   565  	if !osutil.IsExist(candidate) {
   566  		return ""
   567  	}
   568  	return candidate
   569  }