github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/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  	// BinDir must point to a dir that contains compilers required to build
    51  	// older versions of the kernel. For linux, it needs to include several
    52  	// compiler versions.
    53  	BinDir        string `json:"bin_dir"`
    54  	Linker        string `json:"linker"`
    55  	Ccache        string `json:"ccache"`
    56  	KernelRepo    string `json:"kernel_repo"`
    57  	KernelBranch  string `json:"kernel_branch"`
    58  	SyzkallerRepo string `json:"syzkaller_repo"`
    59  	// Directory with user-space system for building kernel images
    60  	// (for linux that's the input to tools/create-gce-image.sh).
    61  	Userspace string `json:"userspace"`
    62  	// Sysctl/cmdline files used to build the image which was used to crash the kernel, e.g. see:
    63  	// dashboard/config/upstream.sysctl
    64  	// dashboard/config/upstream-selinux.cmdline
    65  	Sysctl    string               `json:"sysctl"`
    66  	Cmdline   string               `json:"cmdline"`
    67  	CrossTree bool                 `json:"cross_tree"`
    68  	Backports []vcs.BackportCommit `json:"backports"`
    69  
    70  	KernelConfig         string `json:"kernel_config"`
    71  	KernelBaselineConfig string `json:"kernel_baseline_config"`
    72  
    73  	// Manager config that was used to obtain the crash.
    74  	Manager json.RawMessage `json:"manager"`
    75  }
    76  
    77  func main() {
    78  	flag.Parse()
    79  	os.Setenv("SYZ_DISABLE_SANDBOXING", "yes")
    80  	mycfg := new(Config)
    81  	if err := config.LoadFile(*flagConfig, mycfg); err != nil {
    82  		fmt.Fprintln(os.Stderr, err)
    83  		os.Exit(1)
    84  	}
    85  	mgrcfg, err := mgrconfig.LoadData(mycfg.Manager)
    86  	if err != nil {
    87  		fmt.Fprintln(os.Stderr, err)
    88  		os.Exit(1)
    89  	}
    90  	if mgrcfg.Workdir == "" {
    91  		mgrcfg.Workdir, err = os.MkdirTemp("", "syz-bisect")
    92  		if err != nil {
    93  			fmt.Fprintf(os.Stderr, "failed to create temp dir: %v\n", err)
    94  			os.Exit(1)
    95  		}
    96  		defer os.RemoveAll(mgrcfg.Workdir)
    97  	}
    98  	cfg := &bisect.Config{
    99  		Trace: &debugtracer.GenericTracer{
   100  			TraceWriter: os.Stdout,
   101  			OutDir:      *flagCrash,
   102  		},
   103  		Fix:             *flagFix,
   104  		DefaultCompiler: mycfg.Compiler,
   105  		CompilerType:    mycfg.CompilerType,
   106  		Linker:          mycfg.Linker,
   107  		BinDir:          mycfg.BinDir,
   108  		Ccache:          mycfg.Ccache,
   109  		CrossTree:       mycfg.CrossTree,
   110  		Kernel: bisect.KernelConfig{
   111  			Repo:        mycfg.KernelRepo,
   112  			Branch:      mycfg.KernelBranch,
   113  			Commit:      *flagKernelCommit,
   114  			CommitTitle: *flagKernelCommitTitle,
   115  			Userspace:   mycfg.Userspace,
   116  			Sysctl:      mycfg.Sysctl,
   117  			Cmdline:     mycfg.Cmdline,
   118  			Backports:   mycfg.Backports,
   119  		},
   120  		Syzkaller: bisect.SyzkallerConfig{
   121  			Repo:   mycfg.SyzkallerRepo,
   122  			Commit: *flagSyzkallerCommit,
   123  		},
   124  		Manager: mgrcfg,
   125  	}
   126  	loadFile("", mycfg.KernelConfig, &cfg.Kernel.Config, true)
   127  	loadFile("", mycfg.KernelBaselineConfig, &cfg.Kernel.BaselineConfig, false)
   128  	loadFile(*flagCrash, "repro.prog", &cfg.Repro.Syz, false)
   129  	loadFile(*flagCrash, "repro.cprog", &cfg.Repro.C, false)
   130  	loadFile(*flagCrash, "repro.opts", &cfg.Repro.Opts, false)
   131  
   132  	if len(cfg.Repro.Syz) == 0 && len(cfg.Repro.C) == 0 {
   133  		fmt.Fprintf(os.Stderr, "no repro.cprog or repro.prog found\n")
   134  		os.Exit(1)
   135  	}
   136  
   137  	if cfg.Syzkaller.Commit == "" {
   138  		cfg.Syzkaller.Commit = vcs.HEAD
   139  	}
   140  	if cfg.Kernel.Commit == "" {
   141  		cfg.Kernel.Commit = vcs.HEAD
   142  	}
   143  
   144  	result, err := bisect.Run(cfg)
   145  	if err != nil {
   146  		fmt.Fprintf(os.Stderr, "bisection failed: %v\n", err)
   147  		os.Exit(1)
   148  	}
   149  
   150  	saveResultCommits(result.Commits)
   151  }
   152  
   153  func loadFile(path, file string, dst *[]byte, mandatory bool) {
   154  	filename := filepath.Join(path, file)
   155  	if !mandatory && !osutil.IsExist(filename) {
   156  		return
   157  	}
   158  	data, err := os.ReadFile(filename)
   159  	if err != nil {
   160  		fmt.Fprintln(os.Stderr, err)
   161  		os.Exit(1)
   162  	}
   163  	*dst = data
   164  }
   165  
   166  func saveResultCommits(commits []*vcs.Commit) {
   167  	var result string
   168  	if len(commits) > 0 {
   169  		for _, commit := range commits {
   170  			result = result + commit.Hash + "\n"
   171  		}
   172  	} else if *flagFix {
   173  		result = "the crash still happens on HEAD\n"
   174  	} else {
   175  		result = "the crash already happened on the oldest tested release\n"
   176  	}
   177  
   178  	var fileName string
   179  	if *flagFix {
   180  		fileName = filepath.Join(*flagCrash, "fix.commit")
   181  	} else {
   182  		fileName = filepath.Join(*flagCrash, "cause.commit")
   183  	}
   184  	osutil.WriteFile(fileName, []byte(result))
   185  }