mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-27 05:10:43 +00:00
pkg/bisect: Implement config bisection
Implement Linux kernel configuration bisection. Use bisected minimalistic configuration in commit bisection. Utilizes config_bisect.pl script from Linux kernel tree in bisection. Modify syz-bisect to read in kernel.baseline_config. This is used as a "good" configuration when bisection is run.
This commit is contained in:
parent
d42301aa2f
commit
f8885dc4ce
@ -29,12 +29,18 @@ type Config struct {
|
||||
}
|
||||
|
||||
type KernelConfig struct {
|
||||
Repo string
|
||||
Branch string
|
||||
Commit string
|
||||
Cmdline string
|
||||
Sysctl string
|
||||
Config []byte
|
||||
Repo string
|
||||
Branch string
|
||||
Commit string
|
||||
Cmdline string
|
||||
Sysctl string
|
||||
Config []byte
|
||||
// Baseline configuration is used in commit bisection. If the crash doesn't reproduce
|
||||
// with baseline configuratopm config bisection is run. When triggering configuration
|
||||
// option is found provided baseline configuration is modified according the bisection
|
||||
// results. This new configuration is tested once more with current head. If crash
|
||||
// reproduces with the generated configuration original configuation is replaced with
|
||||
//this minimized one
|
||||
BaselineConfig []byte
|
||||
Userspace string
|
||||
}
|
||||
@ -55,6 +61,7 @@ type env struct {
|
||||
cfg *Config
|
||||
repo vcs.Repo
|
||||
bisecter vcs.Bisecter
|
||||
minimizer vcs.ConfigMinimizer
|
||||
commit *vcs.Commit
|
||||
head *vcs.Commit
|
||||
inst instance.Env
|
||||
@ -80,10 +87,12 @@ const NumTests = 10 // number of tests we do per commit
|
||||
// - no commits in Commits
|
||||
// - the crash report on the oldest release/HEAD;
|
||||
// - Commit points to the oldest/latest commit where crash happens.
|
||||
// - Config contains kernel config used for bisection
|
||||
type Result struct {
|
||||
Commits []*vcs.Commit
|
||||
Report *report.Report
|
||||
Commit *vcs.Commit
|
||||
Config *[]byte
|
||||
NoopChange bool
|
||||
IsRelease bool
|
||||
}
|
||||
@ -103,6 +112,10 @@ func Run(cfg *Config) (*Result, error) {
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("bisection is not implemented for %v", cfg.Manager.TargetOS)
|
||||
}
|
||||
|
||||
// Minimizer may or may not be supported
|
||||
minimizer, _ := repo.(vcs.ConfigMinimizer)
|
||||
|
||||
inst, err := instance.NewEnv(&cfg.Manager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -110,15 +123,17 @@ func Run(cfg *Config) (*Result, error) {
|
||||
if _, err = repo.CheckoutBranch(cfg.Kernel.Repo, cfg.Kernel.Branch); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return runImpl(cfg, repo, bisecter, inst)
|
||||
return runImpl(cfg, repo, bisecter, minimizer, inst)
|
||||
}
|
||||
|
||||
func runImpl(cfg *Config, repo vcs.Repo, bisecter vcs.Bisecter, inst instance.Env) (*Result, error) {
|
||||
func runImpl(cfg *Config, repo vcs.Repo, bisecter vcs.Bisecter, minimizer vcs.ConfigMinimizer,
|
||||
inst instance.Env) (*Result, error) {
|
||||
env := &env{
|
||||
cfg: cfg,
|
||||
repo: repo,
|
||||
bisecter: bisecter,
|
||||
inst: inst,
|
||||
cfg: cfg,
|
||||
repo: repo,
|
||||
bisecter: bisecter,
|
||||
minimizer: minimizer,
|
||||
inst: inst,
|
||||
}
|
||||
head, err := repo.HeadCommit()
|
||||
if err != nil {
|
||||
@ -183,6 +198,7 @@ func (env *env) bisect() (*Result, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
env.commit = com
|
||||
testRes, err := env.test()
|
||||
if err != nil {
|
||||
@ -190,12 +206,18 @@ func (env *env) bisect() (*Result, error) {
|
||||
} else if testRes.verdict != vcs.BisectBad {
|
||||
return nil, fmt.Errorf("the crash wasn't reproduced on the original commit")
|
||||
}
|
||||
|
||||
if cfg.Kernel.BaselineConfig != nil {
|
||||
env.minimizeConfig()
|
||||
}
|
||||
|
||||
bad, good, rep1, results1, err := env.commitRange()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rep1 != nil {
|
||||
return &Result{Report: rep1, Commit: bad}, nil // still not fixed/happens on the oldest release
|
||||
return &Result{Report: rep1, Commit: bad, Config: &cfg.Kernel.Config},
|
||||
nil // still not fixed/happens on the oldest release
|
||||
}
|
||||
results := map[string]*testResult{cfg.Kernel.Commit: testRes}
|
||||
for _, res := range results1 {
|
||||
@ -222,6 +244,7 @@ func (env *env) bisect() (*Result, error) {
|
||||
}
|
||||
res := &Result{
|
||||
Commits: commits,
|
||||
Config: &cfg.Kernel.Config,
|
||||
}
|
||||
if len(commits) == 1 {
|
||||
com := commits[0]
|
||||
@ -244,6 +267,39 @@ func (env *env) bisect() (*Result, error) {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (env *env) minimizeConfig() {
|
||||
cfg := env.cfg
|
||||
// Check if crash reproduces with baseline config
|
||||
originalConfig := cfg.Kernel.Config
|
||||
cfg.Kernel.Config = cfg.Kernel.BaselineConfig
|
||||
testRes, err := env.test()
|
||||
cfg.Kernel.Config = originalConfig
|
||||
if err != nil {
|
||||
env.log("testing baseline config failed")
|
||||
} else if testRes.verdict == vcs.BisectBad {
|
||||
env.log("crash reproduces with baseline config")
|
||||
cfg.Kernel.Config = cfg.Kernel.BaselineConfig
|
||||
} else if testRes.verdict == vcs.BisectGood && env.minimizer != nil {
|
||||
predMinimize := func(test []byte) (vcs.BisectResult, error) {
|
||||
cfg.Kernel.Config = test
|
||||
testRes, err := env.test()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return testRes.verdict, err
|
||||
}
|
||||
// Find minimal configuration based on baseline to reproduce the crash
|
||||
cfg.Kernel.Config, err = env.minimizer.Minimize(cfg.Kernel.Config,
|
||||
cfg.Kernel.BaselineConfig, cfg.Trace, predMinimize)
|
||||
if err != nil {
|
||||
env.log("Minimizing config failed, using original config")
|
||||
cfg.Kernel.Config = originalConfig
|
||||
}
|
||||
} else {
|
||||
env.log("unable to test using baseline config, keep original config")
|
||||
}
|
||||
}
|
||||
|
||||
func (env *env) detectNoopChange(results map[string]*testResult, com *vcs.Commit) (bool, error) {
|
||||
testRes := results[com.Hash]
|
||||
if testRes.kernelSign == "" || len(com.Parents) != 1 {
|
||||
@ -341,6 +397,7 @@ func (env *env) build() (*vcs.Commit, string, error) {
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
bisectEnv, err := env.bisecter.EnvForCommit(env.cfg.BinDir, current.Hash, env.cfg.Kernel.Config)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
|
@ -20,9 +20,11 @@ import (
|
||||
// testEnv will implement instance.BuilderTester. This allows us to
|
||||
// set bisect.env.inst to a testEnv object.
|
||||
type testEnv struct {
|
||||
t *testing.T
|
||||
r vcs.Repo
|
||||
test BisectionTest
|
||||
t *testing.T
|
||||
r vcs.Repo
|
||||
// Kernel config used in "build"
|
||||
config string
|
||||
test BisectionTest
|
||||
}
|
||||
|
||||
func (env *testEnv) BuildSyzkaller(repo, commit string) error {
|
||||
@ -36,17 +38,25 @@ func (env *testEnv) BuildKernel(compilerBin, userspaceDir, cmdlineFile, sysctlFi
|
||||
if commit >= env.test.sameBinaryStart && commit <= env.test.sameBinaryEnd {
|
||||
kernelSign = "same-sign"
|
||||
}
|
||||
env.config = string(kernelConfig)
|
||||
if env.config == "baseline-fails" {
|
||||
return "", kernelSign, fmt.Errorf("Failure")
|
||||
}
|
||||
return "", kernelSign, nil
|
||||
}
|
||||
|
||||
func (env *testEnv) Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]error, error) {
|
||||
commit := env.headCommit()
|
||||
if commit >= env.test.brokenStart && commit <= env.test.brokenEnd {
|
||||
if commit >= env.test.brokenStart && commit <= env.test.brokenEnd ||
|
||||
env.config == "baseline-skip" {
|
||||
return nil, fmt.Errorf("broken build")
|
||||
}
|
||||
if !env.test.fix && commit >= env.test.culprit || env.test.fix && commit < env.test.culprit {
|
||||
if (env.config == "baseline-repro" || env.config == "original config") &&
|
||||
(!env.test.fix && commit >= env.test.culprit || env.test.fix &&
|
||||
commit < env.test.culprit) {
|
||||
return crashErrors(numVMs, "crash occurs"), nil
|
||||
}
|
||||
|
||||
return make([]error, numVMs), nil
|
||||
}
|
||||
|
||||
@ -114,8 +124,10 @@ func runBisection(t *testing.T, baseDir string, test BisectionTest) (*Result, er
|
||||
KernelSrc: baseDir,
|
||||
},
|
||||
Kernel: KernelConfig{
|
||||
Repo: baseDir,
|
||||
Commit: sc.Hash,
|
||||
Repo: baseDir,
|
||||
Commit: sc.Hash,
|
||||
Config: []byte("original config"),
|
||||
BaselineConfig: []byte(test.baselineConfig),
|
||||
},
|
||||
}
|
||||
inst := &testEnv{
|
||||
@ -123,7 +135,7 @@ func runBisection(t *testing.T, baseDir string, test BisectionTest) (*Result, er
|
||||
r: r,
|
||||
test: test,
|
||||
}
|
||||
res, err := runImpl(cfg, r, r.(vcs.Bisecter), inst)
|
||||
res, err := runImpl(cfg, r, r.(vcs.Bisecter), r.(vcs.ConfigMinimizer), inst)
|
||||
t.Log(trace.String())
|
||||
return res, err
|
||||
}
|
||||
@ -146,7 +158,8 @@ type BisectionTest struct {
|
||||
commitLen int
|
||||
oldestLatest int
|
||||
// input and output
|
||||
culprit int
|
||||
culprit int
|
||||
baselineConfig string
|
||||
}
|
||||
|
||||
var bisectionTests = []BisectionTest{
|
||||
@ -158,6 +171,56 @@ var bisectionTests = []BisectionTest{
|
||||
expectRep: true,
|
||||
culprit: 602,
|
||||
},
|
||||
// Test bisection returns correct cause with different baseline/config
|
||||
// combinations
|
||||
{
|
||||
name: "cause-finds-cause-baseline-repro",
|
||||
startCommit: 905,
|
||||
commitLen: 1,
|
||||
expectRep: true,
|
||||
culprit: 602,
|
||||
baselineConfig: "baseline-repro",
|
||||
},
|
||||
{
|
||||
name: "cause-finds-cause-baseline-does-not-repro",
|
||||
startCommit: 905,
|
||||
commitLen: 1,
|
||||
expectRep: true,
|
||||
culprit: 602,
|
||||
baselineConfig: "baseline-not-reproducing",
|
||||
},
|
||||
{
|
||||
name: "cause-finds-cause-baseline-fails",
|
||||
startCommit: 905,
|
||||
commitLen: 1,
|
||||
expectRep: true,
|
||||
culprit: 602,
|
||||
baselineConfig: "baseline-fails",
|
||||
},
|
||||
{
|
||||
name: "cause-finds-cause-baseline-skip",
|
||||
startCommit: 905,
|
||||
commitLen: 1,
|
||||
expectRep: true,
|
||||
culprit: 602,
|
||||
baselineConfig: "baseline-skip",
|
||||
},
|
||||
{
|
||||
name: "cause-finds-cause-minimize_succeeds",
|
||||
startCommit: 905,
|
||||
commitLen: 1,
|
||||
expectRep: true,
|
||||
culprit: 602,
|
||||
baselineConfig: "minimize-succeeds",
|
||||
},
|
||||
{
|
||||
name: "cause-finds-cause-minimize_fails",
|
||||
startCommit: 905,
|
||||
commitLen: 1,
|
||||
expectRep: true,
|
||||
culprit: 602,
|
||||
baselineConfig: "minimize-fails",
|
||||
},
|
||||
// Tests that cause bisection returns error when crash does not reproduce
|
||||
// on the original commit.
|
||||
{
|
||||
|
256
pkg/vcs/linux.go
256
pkg/vcs/linux.go
@ -5,8 +5,12 @@ package vcs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/mail"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
@ -15,6 +19,7 @@ import (
|
||||
|
||||
"github.com/google/syzkaller/pkg/email"
|
||||
"github.com/google/syzkaller/pkg/osutil"
|
||||
"github.com/google/syzkaller/prog"
|
||||
)
|
||||
|
||||
type linux struct {
|
||||
@ -22,6 +27,7 @@ type linux struct {
|
||||
}
|
||||
|
||||
var _ Bisecter = new(linux)
|
||||
var _ ConfigMinimizer = new(linux)
|
||||
|
||||
func newLinux(dir string) *linux {
|
||||
ignoreCC := map[string]bool{
|
||||
@ -240,3 +246,253 @@ func (ctx *linux) getMaintainers(hash string, blame bool) []string {
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
var configBisectTag string = "# Minimized by syzkaller"
|
||||
|
||||
func (ctx *linux) Minimize(original, baseline []byte, trace io.Writer,
|
||||
pred func(test []byte) (BisectResult, error)) ([]byte, error) {
|
||||
if strings.HasPrefix(string(original), configBisectTag) {
|
||||
fmt.Fprintf(trace, "# configuration already minimized\n")
|
||||
return original, nil
|
||||
}
|
||||
bisectDir := "config_bisect"
|
||||
|
||||
suffix := ""
|
||||
i := 0
|
||||
for {
|
||||
bisectDir = "config_bisect" + suffix
|
||||
if !osutil.IsExist(bisectDir) {
|
||||
fmt.Fprintf(trace, "# using %v as config bisect directory\n", bisectDir)
|
||||
break
|
||||
}
|
||||
suffix = strconv.Itoa(i)
|
||||
i++
|
||||
}
|
||||
kernelConfig := filepath.Join(bisectDir, "kernel.config")
|
||||
kernelBaselineConfig := filepath.Join(bisectDir, "kernel.baseline_config")
|
||||
|
||||
err := os.MkdirAll(bisectDir, 0755)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create dir for config bisect: %v", err)
|
||||
}
|
||||
|
||||
err = ctx.prepareConfigBisectEnv(kernelConfig, kernelBaselineConfig, original, baseline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(ctx.git.dir+"/tools/testing/ktest/config-bisect.pl",
|
||||
"-l", ctx.git.dir, "-r", "-b", ctx.git.dir, kernelBaselineConfig, kernelConfig)
|
||||
|
||||
configBisectName := filepath.Join(bisectDir, "config_bisect.log")
|
||||
configBisectLog, err := os.Create(configBisectName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create bisect log file: %v", err)
|
||||
}
|
||||
defer configBisectLog.Close()
|
||||
cmd.Stdout = configBisectLog
|
||||
cmd.Stderr = configBisectLog
|
||||
|
||||
fmt.Fprintf(trace, "# start config bisection\n")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("config bisect failed: %v", err)
|
||||
}
|
||||
|
||||
verdict := ""
|
||||
var configOptions []string
|
||||
for {
|
||||
config, err := ioutil.ReadFile(filepath.Join(ctx.git.dir,
|
||||
".config"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read .config: %v", err)
|
||||
}
|
||||
|
||||
testRes, err := pred(config)
|
||||
if err != nil {
|
||||
break
|
||||
} else if testRes == BisectBad {
|
||||
verdict = "bad"
|
||||
} else if testRes == BisectGood {
|
||||
verdict = "good"
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to test, stopping config bisection: %v", err)
|
||||
}
|
||||
|
||||
cmd = exec.Command(ctx.git.dir+"/tools/testing/ktest/config-bisect.pl",
|
||||
"-l", ctx.git.dir, "-b", ctx.git.dir,
|
||||
kernelBaselineConfig, kernelConfig, verdict)
|
||||
|
||||
cmd.Stdout = configBisectLog
|
||||
cmd.Stderr = configBisectLog
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
if fmt.Sprint(err) != "exit status 2" {
|
||||
return nil, fmt.Errorf("config bisect failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(trace, "# config_bisect.pl finished or failed: %v\n", err)
|
||||
logForParsing, err := ioutil.ReadFile(configBisectName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read config_bisect.pl log: %v", err)
|
||||
}
|
||||
configOptions = append(configOptions, ctx.parseConfigBisectLog(trace, logForParsing)...)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil || len(configOptions) == 0 {
|
||||
return nil, fmt.Errorf("configuration bisection failed: %v", err)
|
||||
}
|
||||
|
||||
// Parse minimalistic configuration to generate the crash
|
||||
minimizedConfig, err := ctx.generateMinConfig(configOptions, bisectDir,
|
||||
kernelBaselineConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generating minimized config failed (%v)", err)
|
||||
}
|
||||
|
||||
// Check that crash is really reproduced with generated config
|
||||
testRes, err := pred(minimizedConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("testing generated minimized config failed (%v)", err)
|
||||
}
|
||||
if testRes == BisectGood {
|
||||
fmt.Fprintf(trace, "# testing with generated minimized config doesn't reproduce the crash\n")
|
||||
return original, nil
|
||||
}
|
||||
return minimizedConfig, nil
|
||||
}
|
||||
|
||||
func (ctx *linux) prepareConfigBisectEnv(kernelConfig, kernelBaselineConfig string, original, baseline []byte) error {
|
||||
current, err := ctx.HeadCommit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Call EnvForCommit if some options needs to be adjusted
|
||||
bisectEnv, err := ctx.EnvForCommit("", current.Hash, original)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed create commit environment: %v", err)
|
||||
}
|
||||
if err := osutil.WriteFile(kernelConfig, bisectEnv.KernelConfig); err != nil {
|
||||
return fmt.Errorf("failed to write config file: %v", err)
|
||||
}
|
||||
|
||||
// Call EnvForCommit again if some options needs to be adjusted in baseline
|
||||
bisectEnv, err = ctx.EnvForCommit("", current.Hash, baseline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed create commit environment: %v", err)
|
||||
}
|
||||
if err := osutil.WriteFile(kernelBaselineConfig, bisectEnv.KernelConfig); err != nil {
|
||||
return fmt.Errorf("failed to write minimum config file: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Takes in config_bisect.pl output:
|
||||
// Hmm, can't make any more changes without making good == bad?
|
||||
// Difference between good (+) and bad (-)
|
||||
// +DRIVER1=n
|
||||
// +DRIVER2=n
|
||||
// -DRIVER3=n
|
||||
// -DRIVER4=n
|
||||
// DRIVER5 n -> y
|
||||
// DRIVER6 y -> n
|
||||
// See good and bad configs for details:
|
||||
// good: /mnt/work/linux/good_config.tmp
|
||||
// bad: /mnt/work/linux/bad_config.tmp
|
||||
func (ctx *linux) parseConfigBisectLog(trace io.Writer, bisectLog []byte) []string {
|
||||
var configOptions []string
|
||||
start := false
|
||||
for _, line := range strings.Split(string(bisectLog), "\n") {
|
||||
if strings.Contains(line, "See good and bad configs for details:") {
|
||||
break
|
||||
}
|
||||
if start {
|
||||
selection := ""
|
||||
option := ""
|
||||
configOptioon := ""
|
||||
if strings.HasPrefix(line, "+") {
|
||||
// This is option only in good config. Drop it as it's dependent
|
||||
// on some option which is disabled in bad config.
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "-") {
|
||||
// -CONFIG_DRIVER_1=n
|
||||
// Remove preceding -1 and split to option and selection
|
||||
fields := strings.Split(strings.TrimPrefix(line, "-"), "=")
|
||||
option = fields[0]
|
||||
selection = fields[len(fields)-1]
|
||||
} else {
|
||||
// DRIVER_OPTION1 n -> y
|
||||
fields := strings.Split(strings.TrimPrefix(line, " "), " ")
|
||||
option = fields[0]
|
||||
selection = fields[len(fields)-1]
|
||||
}
|
||||
|
||||
if selection == "n" {
|
||||
configOptioon = "# CONFIG_" + option + " is not set"
|
||||
} else {
|
||||
configOptioon = "CONFIG_" + option + "=" + selection
|
||||
}
|
||||
|
||||
configOptions = append(configOptions, configOptioon)
|
||||
}
|
||||
if strings.Contains(line, "Difference between good (+) and bad (-)") {
|
||||
start = true
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(trace, "# found config option changes %v\n", configOptions)
|
||||
return configOptions
|
||||
}
|
||||
|
||||
func (ctx *linux) generateMinConfig(configOptions []string, outdir string, baseline string) ([]byte, error) {
|
||||
kernelAdditionsConfig := filepath.Join(outdir, "kernel.additions_config")
|
||||
configMergeOutput := filepath.Join(outdir, ".config")
|
||||
kernelMinConfig := filepath.Join(outdir, filepath.Base(baseline)+"_modified")
|
||||
|
||||
additionsFile, err := os.OpenFile(kernelAdditionsConfig, os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create config additions file: %v", err)
|
||||
}
|
||||
defer additionsFile.Close()
|
||||
|
||||
for _, line := range configOptions {
|
||||
if _, err := additionsFile.WriteString(line + "\n"); err != nil {
|
||||
return nil, fmt.Errorf("failed to write config additions file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command(ctx.git.dir+"/scripts/kconfig/merge_config.sh", "-m",
|
||||
"-O", outdir, baseline, kernelAdditionsConfig)
|
||||
|
||||
configMergeLog, err := os.Create(filepath.Join(outdir, "config_merge.log"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create merge log file: %v", err)
|
||||
}
|
||||
defer configMergeLog.Close()
|
||||
cmd.Stdout = configMergeLog
|
||||
cmd.Stderr = configMergeLog
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("config merge failed: %v", err)
|
||||
}
|
||||
|
||||
minConfig, err := ioutil.ReadFile(configMergeOutput)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read merged configuration: %v", err)
|
||||
}
|
||||
|
||||
minConfigWithTag := []byte(configBisectTag)
|
||||
minConfigWithTag = append(minConfigWithTag, []byte(", rev: ")...)
|
||||
minConfigWithTag = append(minConfigWithTag, []byte(prog.GitRevision)...)
|
||||
minConfigWithTag = append(minConfigWithTag, []byte("\n")...)
|
||||
minConfigWithTag = append(minConfigWithTag, minConfig...)
|
||||
|
||||
if err := osutil.WriteFile(kernelMinConfig, minConfigWithTag); err != nil {
|
||||
return nil, fmt.Errorf("failed to write tagged minimum config file: %v", err)
|
||||
}
|
||||
return minConfigWithTag, nil
|
||||
}
|
||||
|
114
pkg/vcs/linux_test.go
Normal file
114
pkg/vcs/linux_test.go
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright 2019 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 vcs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/syzkaller/pkg/osutil"
|
||||
)
|
||||
|
||||
type MinimizationTest struct {
|
||||
config string
|
||||
baselineConfig string
|
||||
// Output contains expected config option
|
||||
expectedConfig string
|
||||
// Minimization is expected to pass or fail
|
||||
passing bool
|
||||
}
|
||||
|
||||
func createTestLinuxRepo(t *testing.T) string {
|
||||
baseDir, err := ioutil.TempDir("", "syz-config-bisect-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repo := CreateTestRepo(t, baseDir, "")
|
||||
repo.CommitChange("commit")
|
||||
repo.SetTag("v4.1")
|
||||
err = os.MkdirAll(baseDir+"/tools/testing/ktest", 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = os.MkdirAll(baseDir+"/scripts/kconfig", 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Copy stubbed scripts used by config bisect
|
||||
err = osutil.CopyFile("testdata/linux/config-bisect.pl",
|
||||
baseDir+"/tools/testing/ktest/config-bisect.pl")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = osutil.CopyFile("testdata/linux/merge_config.sh",
|
||||
baseDir+"/scripts/kconfig/merge_config.sh")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return baseDir
|
||||
}
|
||||
|
||||
func TestMinimizationResults(t *testing.T) {
|
||||
tests := []MinimizationTest{
|
||||
{
|
||||
config: "CONFIG_ORIGINAL=y",
|
||||
baselineConfig: "CONFIG_FAILING=y",
|
||||
expectedConfig: "CONFIG_ORIGINAL=y",
|
||||
passing: false,
|
||||
},
|
||||
{
|
||||
config: "CONFIG_ORIGINAL=y",
|
||||
baselineConfig: "CONFIG_REPRODUCES_CRASH=y",
|
||||
expectedConfig: "CONFIG_REPRODUCES_CRASH=y",
|
||||
passing: true,
|
||||
},
|
||||
{
|
||||
config: "CONFIG_ORIGINAL=y",
|
||||
baselineConfig: "CONFIG_NOT_REPRODUCE_CRASH=y",
|
||||
expectedConfig: "CONFIG_ORIGINAL=y",
|
||||
passing: true,
|
||||
},
|
||||
{
|
||||
config: configBisectTag,
|
||||
baselineConfig: "CONFIG_NOT_REPRODUCE_CRASH=y",
|
||||
expectedConfig: configBisectTag,
|
||||
passing: true,
|
||||
},
|
||||
}
|
||||
|
||||
trace := new(bytes.Buffer)
|
||||
baseDir := createTestLinuxRepo(t)
|
||||
repo, err := NewRepo("linux", "64", baseDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create repository")
|
||||
}
|
||||
pred := func(test []byte) (BisectResult, error) {
|
||||
if strings.Contains(string(test), "CONFIG_REPRODUCES_CRASH=y") {
|
||||
return BisectBad, nil
|
||||
}
|
||||
return BisectGood, nil
|
||||
}
|
||||
|
||||
minimizer, ok := repo.(ConfigMinimizer)
|
||||
if !ok {
|
||||
t.Fatalf("Config minimization is not implemented")
|
||||
}
|
||||
for _, test := range tests {
|
||||
outConfig, err := minimizer.Minimize([]byte(test.config),
|
||||
[]byte(test.baselineConfig), trace, pred)
|
||||
if test.passing && err != nil {
|
||||
t.Fatalf("Failed to run Minimize")
|
||||
} else if test.passing && !strings.Contains(string(outConfig),
|
||||
test.expectedConfig) {
|
||||
t.Fatalf("Output is not expected %v vs. %v", string(outConfig),
|
||||
test.expectedConfig)
|
||||
}
|
||||
}
|
||||
t.Log(trace.String())
|
||||
}
|
53
pkg/vcs/testdata/linux/config-bisect.pl
vendored
Executable file
53
pkg/vcs/testdata/linux/config-bisect.pl
vendored
Executable file
@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2020 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.
|
||||
# config-bisect.pl -l ctx.git.dir -r -b ctx.git.dir kernelBaselineConfig kernelConfig
|
||||
|
||||
if [ "$3" == "-r" ]
|
||||
then
|
||||
baseline=`cat $6`
|
||||
outdir=$5
|
||||
echo $baseline > $outdir/.config
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# config-bisect.pl -l ctx.git.dir -b ctx.git.dir kernelBaselineConfig kernelConfig verdict
|
||||
baseline=`cat $5`
|
||||
|
||||
# Test baseline file contains string CONFIG_FAILING -> fail
|
||||
if [ "$baseline" == "CONFIG_FAILING=y" ]
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Generate end results which "reproduces" the crash
|
||||
if [ $baseline == "CONFIG_REPRODUCES_CRASH=y" ]
|
||||
then
|
||||
echo "%%%%%%%% FAILED TO FIND SINGLE BAD CONFIG %%%%%%%%"
|
||||
echo "Hmm, can't make any more changes without making good == bad?"
|
||||
echo "Difference between good (+) and bad (-)"
|
||||
echo "REPRODUCES_CRASH n -> y"
|
||||
echo "-DISABLED_OPTION=n"
|
||||
echo "+ONLY_IN_ORIGINAL_OPTION=y"
|
||||
echo "See good and bad configs for details:"
|
||||
echo "good: /mnt/work/config_bisect_evaluation/out/config_bisect/kernel.baseline_config.tmp"
|
||||
echo "bad: /mnt/work/config_bisect_evaluation/out/config_bisect/kernel.config.tmp"
|
||||
echo "%%%%%%%% FAILED TO FIND SINGLE BAD CONFIG %%%%%%%%"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Generate end result which doesn't "reproduce" the crash
|
||||
if [ $baseline == "CONFIG_NOT_REPRODUCE_CRASH=y" ]
|
||||
then
|
||||
echo "%%%%%%%% FAILED TO FIND SINGLE BAD CONFIG %%%%%%%%"
|
||||
echo "Hmm, can't make any more changes without making good == bad?"
|
||||
echo "Difference between good (+) and bad (-)"
|
||||
echo "NOT_REPRODUCE_CRASH n -> y"
|
||||
echo "See good and bad configs for details:"
|
||||
echo "good: /mnt/work/config_bisect_evaluation/out/config_bisect/kernel.baseline_config.tmp"
|
||||
echo "bad: /mnt/work/config_bisect_evaluation/out/config_bisect/kernel.config.tmp"
|
||||
echo "%%%%%%%% FAILED TO FIND SINGLE BAD CONFIG %%%%%%%%"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
|
11
pkg/vcs/testdata/linux/merge_config.sh
vendored
Executable file
11
pkg/vcs/testdata/linux/merge_config.sh
vendored
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2020 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.
|
||||
|
||||
# merge_config.sh -m -O outdir baseline kernelAdditionsConfig
|
||||
OUTDIR=$3
|
||||
|
||||
echo `cat $4` > $OUTDIR/.config
|
||||
echo `cat $5` >> $OUTDIR/.config
|
||||
|
||||
exit 0
|
@ -3,10 +3,17 @@
|
||||
|
||||
package vcs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type testos struct {
|
||||
*git
|
||||
}
|
||||
|
||||
var _ ConfigMinimizer = new(testos)
|
||||
|
||||
func newTestos(dir string) *testos {
|
||||
return &testos{
|
||||
git: newGit(dir, nil),
|
||||
@ -18,5 +25,13 @@ func (ctx *testos) PreviousReleaseTags(commit string) ([]string, error) {
|
||||
}
|
||||
|
||||
func (ctx *testos) EnvForCommit(binDir, commit string, kernelConfig []byte) (*BisectEnv, error) {
|
||||
return &BisectEnv{}, nil
|
||||
return &BisectEnv{KernelConfig: kernelConfig}, nil
|
||||
}
|
||||
|
||||
func (ctx *testos) Minimize(original, baseline []byte, trace io.Writer,
|
||||
pred func(test []byte) (BisectResult, error)) ([]byte, error) {
|
||||
if string(baseline) == "minimize-fails" {
|
||||
return nil, fmt.Errorf("minimization failure")
|
||||
}
|
||||
return original, nil
|
||||
}
|
||||
|
@ -69,6 +69,10 @@ type Bisecter interface {
|
||||
EnvForCommit(binDir, commit string, kernelConfig []byte) (*BisectEnv, error)
|
||||
}
|
||||
|
||||
type ConfigMinimizer interface {
|
||||
Minimize(original, baseline []byte, trace io.Writer, pred func(test []byte) (BisectResult, error)) ([]byte, error)
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
Hash string
|
||||
Title string
|
||||
|
@ -102,9 +102,11 @@ func main() {
|
||||
loadString("syzkaller.commit", &cfg.Syzkaller.Commit)
|
||||
loadString("kernel.commit", &cfg.Kernel.Commit)
|
||||
loadFile("kernel.config", &cfg.Kernel.Config, true)
|
||||
loadFile("kernel.baseline_config", &cfg.Kernel.BaselineConfig, false)
|
||||
loadFile("repro.syz", &cfg.Repro.Syz, false)
|
||||
loadFile("repro.c", &cfg.Repro.C, false)
|
||||
loadFile("repro.opts", &cfg.Repro.Opts, true)
|
||||
|
||||
if _, err := bisect.Run(cfg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "bisection failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
Loading…
Reference in New Issue
Block a user