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

     1  // Copyright 2018 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-bisect runs bisection to find cause/fix commit for a crash.
     5  //
     6  // The tool is originally created to test pkg/bisect logic.
     7  //
     8  // The tool requires a config file passed in -config flag, see Config type below for details,
     9  // and a directory with info about the crash passed in -crash flag).
    10  // If -fix flag is specified, it does fix bisection. Otherwise it does cause bisection. Also
    11  // wanted syzkaller and kernel commits can be specified using -syzkaller_commit and
    12  // -kernel_commit. HEAD is used if commits are not specified.
    13  //
    14  // The crash dir should contain the following files:
    15  //   - repro.cprog or repro.prog: reproducer for the crash
    16  //   - repro.opts: syzkaller reproducer options (e.g. {"procs":1,"sandbox":"none",...}) (optional)
    17  //
    18  // The tool stores bisection result into cause.commit or fix.commit.
    19  package main
    20  
    21  import (
    22  	"encoding/json"
    23  	"flag"
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	"github.com/google/syzkaller/pkg/bisect"
    29  	"github.com/google/syzkaller/pkg/config"
    30  	"github.com/google/syzkaller/pkg/debugtracer"
    31  	"github.com/google/syzkaller/pkg/mgrconfig"
    32  	"github.com/google/syzkaller/pkg/osutil"
    33  	"github.com/google/syzkaller/pkg/vcs"
    34  )
    35  
    36  var (
    37  	flagConfig            = flag.String("config", "", "bisect config file")
    38  	flagCrash             = flag.String("crash", "", "dir with crash info")
    39  	flagFix               = flag.Bool("fix", false, "search for crash fix")
    40  	flagKernelCommit      = flag.String("kernel_commit", "", "original kernel commit")
    41  	flagKernelCommitTitle = flag.String("kernel_commit_title", "", "original kernel commit title")
    42  	flagSyzkallerCommit   = flag.String("syzkaller_commit", "", "original syzkaller commit")
    43  )
    44  
    45  type Config struct {
    46  	Compiler string `json:"compiler"`
    47  	// Currently either 'gcc' or 'clang'. Note that pkg/bisect requires
    48  	// explicit plumbing for every os/compiler combination.
    49  	CompilerType string `json:"compiler_type"`
    50  	Make         string `json:"make"`
    51  	// BinDir must point to a dir that contains compilers required to build
    52  	// older versions of the kernel. For linux, it needs to include several
    53  	// compiler versions.
    54  	BinDir        string `json:"bin_dir"`
    55  	Linker        string `json:"linker"`
    56  	Ccache        string `json:"ccache"`
    57  	KernelRepo    string `json:"kernel_repo"`
    58  	KernelBranch  string `json:"kernel_branch"`
    59  	SyzkallerRepo string `json:"syzkaller_repo"`
    60  	// Directory with user-space system for building kernel images
    61  	// (for linux that's the input to tools/create-gce-image.sh).
    62  	Userspace string `json:"userspace"`
    63  	// Sysctl/cmdline files used to build the image which was used to crash the kernel, e.g. see:
    64  	// dashboard/config/upstream.sysctl
    65  	// dashboard/config/upstream-selinux.cmdline
    66  	Sysctl    string               `json:"sysctl"`
    67  	Cmdline   string               `json:"cmdline"`
    68  	CrossTree bool                 `json:"cross_tree"`
    69  	Backports []vcs.BackportCommit `json:"backports"`
    70  
    71  	KernelConfig         string `json:"kernel_config"`
    72  	KernelBaselineConfig string `json:"kernel_baseline_config"`
    73  
    74  	// Manager config that was used to obtain the crash.
    75  	Manager json.RawMessage `json:"manager"`
    76  }
    77  
    78  func main() {
    79  	flag.Parse()
    80  	os.Setenv("SYZ_DISABLE_SANDBOXING", "yes")
    81  	mycfg := new(Config)
    82  	if err := config.LoadFile(*flagConfig, mycfg); err != nil {
    83  		fmt.Fprintln(os.Stderr, err)
    84  		os.Exit(1)
    85  	}
    86  	mgrcfg, err := mgrconfig.LoadData(mycfg.Manager)
    87  	if err != nil {
    88  		fmt.Fprintln(os.Stderr, err)
    89  		os.Exit(1)
    90  	}
    91  	if mgrcfg.Workdir == "" {
    92  		mgrcfg.Workdir, err = os.MkdirTemp("", "syz-bisect")
    93  		if err != nil {
    94  			fmt.Fprintf(os.Stderr, "failed to create temp dir: %v\n", err)
    95  			os.Exit(1)
    96  		}
    97  		defer os.RemoveAll(mgrcfg.Workdir)
    98  	}
    99  	cfg := &bisect.Config{
   100  		Trace: &debugtracer.GenericTracer{
   101  			TraceWriter: os.Stdout,
   102  			OutDir:      *flagCrash,
   103  		},
   104  		Fix:             *flagFix,
   105  		DefaultCompiler: mycfg.Compiler,
   106  		CompilerType:    mycfg.CompilerType,
   107  		Make:            mycfg.Make,
   108  		Linker:          mycfg.Linker,
   109  		BinDir:          mycfg.BinDir,
   110  		Ccache:          mycfg.Ccache,
   111  		CrossTree:       mycfg.CrossTree,
   112  		Kernel: bisect.KernelConfig{
   113  			Repo:        mycfg.KernelRepo,
   114  			Branch:      mycfg.KernelBranch,
   115  			Commit:      *flagKernelCommit,
   116  			CommitTitle: *flagKernelCommitTitle,
   117  			Userspace:   mycfg.Userspace,
   118  			Sysctl:      mycfg.Sysctl,
   119  			Cmdline:     mycfg.Cmdline,
   120  			Backports:   mycfg.Backports,
   121  		},
   122  		Syzkaller: bisect.SyzkallerConfig{
   123  			Repo:   mycfg.SyzkallerRepo,
   124  			Commit: *flagSyzkallerCommit,
   125  		},
   126  		Manager: mgrcfg,
   127  	}
   128  	loadFile("", mycfg.KernelConfig, &cfg.Kernel.Config, true)
   129  	loadFile("", mycfg.KernelBaselineConfig, &cfg.Kernel.BaselineConfig, false)
   130  	loadFile(*flagCrash, "repro.prog", &cfg.Repro.Syz, false)
   131  	loadFile(*flagCrash, "repro.cprog", &cfg.Repro.C, false)
   132  	loadFile(*flagCrash, "repro.opts", &cfg.Repro.Opts, false)
   133  
   134  	if len(cfg.Repro.Syz) == 0 && len(cfg.Repro.C) == 0 {
   135  		fmt.Fprintf(os.Stderr, "no repro.cprog or repro.prog found\n")
   136  		os.Exit(1)
   137  	}
   138  
   139  	if cfg.Syzkaller.Commit == "" {
   140  		cfg.Syzkaller.Commit = vcs.HEAD
   141  	}
   142  	if cfg.Kernel.Commit == "" {
   143  		cfg.Kernel.Commit = vcs.HEAD
   144  	}
   145  
   146  	result, err := bisect.Run(cfg)
   147  	if err != nil {
   148  		fmt.Fprintf(os.Stderr, "bisection failed: %v\n", err)
   149  		os.Exit(1)
   150  	}
   151  
   152  	saveResultCommits(result.Commits)
   153  }
   154  
   155  func loadFile(path, file string, dst *[]byte, mandatory bool) {
   156  	filename := filepath.Join(path, file)
   157  	if !mandatory && !osutil.IsExist(filename) {
   158  		return
   159  	}
   160  	data, err := os.ReadFile(filename)
   161  	if err != nil {
   162  		fmt.Fprintln(os.Stderr, err)
   163  		os.Exit(1)
   164  	}
   165  	*dst = data
   166  }
   167  
   168  func saveResultCommits(commits []*vcs.Commit) {
   169  	var result string
   170  	if len(commits) > 0 {
   171  		for _, commit := range commits {
   172  			result = result + commit.Hash + "\n"
   173  		}
   174  	} else if *flagFix {
   175  		result = "the crash still happens on HEAD\n"
   176  	} else {
   177  		result = "the crash already happened on the oldest tested release\n"
   178  	}
   179  
   180  	var fileName string
   181  	if *flagFix {
   182  		fileName = filepath.Join(*flagCrash, "fix.commit")
   183  	} else {
   184  		fileName = filepath.Join(*flagCrash, "cause.commit")
   185  	}
   186  	osutil.WriteFile(fileName, []byte(result))
   187  }