diff options
| -rw-r--r-- | config/config.go | 82 | ||||
| -rw-r--r-- | config/config_test.go | 2 | ||||
| -rw-r--r-- | report/report.go | 15 | ||||
| -rw-r--r-- | report/report_test.go | 51 | ||||
| -rw-r--r-- | repro/repro.go | 4 | ||||
| -rw-r--r-- | syz-manager/manager.go | 13 | ||||
| -rw-r--r-- | tools/syz-crush/crush.go | 4 | ||||
| -rw-r--r-- | tools/syz-report/syz-report.go | 2 | ||||
| -rw-r--r-- | tools/syz-repro/repro.go | 2 | ||||
| -rw-r--r-- | tools/syz-symbolize/symbolize.go | 2 | ||||
| -rw-r--r-- | vm/vm.go | 9 |
11 files changed, 123 insertions, 63 deletions
diff --git a/config/config.go b/config/config.go index 33e9c625e..b7492ad68 100644 --- a/config/config.go +++ b/config/config.go @@ -58,82 +58,87 @@ type Config struct { Enable_Syscalls []string Disable_Syscalls []string - Suppressions []string + Suppressions []string // don't save reports matching these regexps, but reboot VM after them + Ignores []string // completely ignore reports matching these regexps (don't save nor reboot) + + // Implementation details beyond this point. + ParsedSuppressions []*regexp.Regexp `json:"-"` + ParsedIgnores []*regexp.Regexp `json:"-"` } -func Parse(filename string) (*Config, map[int]bool, []*regexp.Regexp, error) { +func Parse(filename string) (*Config, map[int]bool, error) { if filename == "" { - return nil, nil, nil, fmt.Errorf("supply config in -config flag") + return nil, nil, fmt.Errorf("supply config in -config flag") } data, err := ioutil.ReadFile(filename) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to read config file: %v", err) + return nil, nil, fmt.Errorf("failed to read config file: %v", err) } return parse(data) } -func parse(data []byte) (*Config, map[int]bool, []*regexp.Regexp, error) { +func parse(data []byte) (*Config, map[int]bool, error) { unknown, err := checkUnknownFields(data) if err != nil { - return nil, nil, nil, err + return nil, nil, err } if unknown != "" { - return nil, nil, nil, fmt.Errorf("unknown field '%v' in config", unknown) + return nil, nil, fmt.Errorf("unknown field '%v' in config", unknown) } cfg := new(Config) cfg.Cover = true cfg.Sandbox = "setuid" if err := json.Unmarshal(data, cfg); err != nil { - return nil, nil, nil, fmt.Errorf("failed to parse config file: %v", err) + return nil, nil, fmt.Errorf("failed to parse config file: %v", err) } if _, err := os.Stat(filepath.Join(cfg.Syzkaller, "bin/syz-fuzzer")); err != nil { - return nil, nil, nil, fmt.Errorf("bad config syzkaller param: can't find bin/syz-fuzzer") + return nil, nil, fmt.Errorf("bad config syzkaller param: can't find bin/syz-fuzzer") } if _, err := os.Stat(filepath.Join(cfg.Syzkaller, "bin/syz-executor")); err != nil { - return nil, nil, nil, fmt.Errorf("bad config syzkaller param: can't find bin/syz-executor") + return nil, nil, fmt.Errorf("bad config syzkaller param: can't find bin/syz-executor") } if cfg.Http == "" { - return nil, nil, nil, fmt.Errorf("config param http is empty") + return nil, nil, fmt.Errorf("config param http is empty") } if cfg.Workdir == "" { - return nil, nil, nil, fmt.Errorf("config param workdir is empty") + return nil, nil, fmt.Errorf("config param workdir is empty") } if cfg.Vmlinux == "" { - return nil, nil, nil, fmt.Errorf("config param vmlinux is empty") + return nil, nil, fmt.Errorf("config param vmlinux is empty") } if cfg.Type == "" { - return nil, nil, nil, fmt.Errorf("config param type is empty") + return nil, nil, fmt.Errorf("config param type is empty") } switch cfg.Type { case "none": if cfg.Count != 0 { - return nil, nil, nil, fmt.Errorf("invalid config param count: %v, type \"none\" does not support param count", cfg.Count) + return nil, nil, fmt.Errorf("invalid config param count: %v, type \"none\" does not support param count", cfg.Count) } if cfg.Rpc == "" { - return nil, nil, nil, fmt.Errorf("config param rpc is empty (required for type \"none\")") + return nil, nil, fmt.Errorf("config param rpc is empty (required for type \"none\")") } if len(cfg.Devices) != 0 { - return nil, nil, nil, fmt.Errorf("type %v does not support devices param", cfg.Type) + return nil, nil, fmt.Errorf("type %v does not support devices param", cfg.Type) } case "adb": if cfg.Count != 0 { - return nil, nil, nil, fmt.Errorf("don't specify count for adb, instead specify devices") + return nil, nil, fmt.Errorf("don't specify count for adb, instead specify devices") } if len(cfg.Devices) == 0 { - return nil, nil, nil, fmt.Errorf("specify at least 1 adb device") + return nil, nil, fmt.Errorf("specify at least 1 adb device") } cfg.Count = len(cfg.Devices) case "gce": if cfg.Machine_Type == "" { - return nil, nil, nil, fmt.Errorf("machine_type parameter is empty (required for gce)") + return nil, nil, fmt.Errorf("machine_type parameter is empty (required for gce)") } fallthrough default: if cfg.Count <= 0 || cfg.Count > 1000 { - return nil, nil, nil, fmt.Errorf("invalid config param count: %v, want (1, 1000]", cfg.Count) + return nil, nil, fmt.Errorf("invalid config param count: %v, want (1, 1000]", cfg.Count) } if len(cfg.Devices) != 0 { - return nil, nil, nil, fmt.Errorf("type %v does not support devices param", cfg.Type) + return nil, nil, fmt.Errorf("type %v does not support devices param", cfg.Type) } } if cfg.Rpc == "" { @@ -143,7 +148,7 @@ func parse(data []byte) (*Config, map[int]bool, []*regexp.Regexp, error) { cfg.Procs = 1 } if cfg.Procs > 32 { - return nil, nil, nil, fmt.Errorf("config param procs has higher value '%v' then the max supported 32", cfg.Procs) + return nil, nil, fmt.Errorf("config param procs has higher value '%v' then the max supported 32", cfg.Procs) } if cfg.Output == "" { if cfg.Type == "local" { @@ -155,25 +160,24 @@ func parse(data []byte) (*Config, map[int]bool, []*regexp.Regexp, error) { switch cfg.Output { case "none", "stdout", "dmesg", "file": default: - return nil, nil, nil, fmt.Errorf("config param output must contain one of none/stdout/dmesg/file") + return nil, nil, fmt.Errorf("config param output must contain one of none/stdout/dmesg/file") } switch cfg.Sandbox { case "none", "setuid", "namespace": default: - return nil, nil, nil, fmt.Errorf("config param sandbox must contain one of none/setuid/namespace") + return nil, nil, fmt.Errorf("config param sandbox must contain one of none/setuid/namespace") } syscalls, err := parseSyscalls(cfg) if err != nil { - return nil, nil, nil, err + return nil, nil, err } - suppressions, err := parseSuppressions(cfg) - if err != nil { - return nil, nil, nil, err + if err := parseSuppressions(cfg); err != nil { + return nil, nil, err } - return cfg, syscalls, suppressions, nil + return cfg, syscalls, nil } func parseSyscalls(cfg *Config) (map[int]bool, error) { @@ -225,7 +229,7 @@ func parseSyscalls(cfg *Config) (map[int]bool, error) { return syscalls, nil } -func parseSuppressions(cfg *Config) ([]*regexp.Regexp, error) { +func parseSuppressions(cfg *Config) error { // Add some builtin suppressions. supp := append(cfg.Suppressions, []string{ "panic: failed to start executor binary", @@ -237,16 +241,21 @@ func parseSuppressions(cfg *Config) ([]*regexp.Regexp, error) { "lowmemorykiller: Killing 'syz-fuzzer'", //"INFO: lockdep is turned off", // printed by some sysrq that dumps scheduler state, but also on all lockdep reports }...) - var suppressions []*regexp.Regexp for _, s := range supp { re, err := regexp.Compile(s) if err != nil { - return nil, fmt.Errorf("failed to compile suppression '%v': %v", s, err) + return fmt.Errorf("failed to compile suppression '%v': %v", s, err) } - suppressions = append(suppressions, re) + cfg.ParsedSuppressions = append(cfg.ParsedSuppressions, re) } - - return suppressions, nil + for _, ignore := range cfg.Ignores { + re, err := regexp.Compile(ignore) + if err != nil { + return fmt.Errorf("failed to compile ignore '%v': %v", ignore, err) + } + cfg.ParsedIgnores = append(cfg.ParsedIgnores, re) + } + return nil } func CreateVMConfig(cfg *Config, index int) (*vm.Config, error) { @@ -313,6 +322,7 @@ func checkUnknownFields(data []byte) (string, error) { "Enable_Syscalls", "Disable_Syscalls", "Suppressions", + "Ignores", "Initrd", "Machine_Type", } diff --git a/config/config_test.go b/config/config_test.go index 2a465900d..1072587df 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -9,7 +9,7 @@ import ( func TestUnknown(t *testing.T) { data := `{"foo": "bar"}` - _, _, _, err := parse([]byte(data)) + _, _, err := parse([]byte(data)) if err == nil || err.Error() != "unknown field 'foo' in config" { t.Fatalf("unknown field is not detected (%v)", err) } diff --git a/report/report.go b/report/report.go index 0e5a04bba..b82b9b9a7 100644 --- a/report/report.go +++ b/report/report.go @@ -237,7 +237,7 @@ func compile(re string) *regexp.Regexp { } // ContainsCrash searches kernel console output for oops messages. -func ContainsCrash(output []byte) bool { +func ContainsCrash(output []byte, ignores []*regexp.Regexp) bool { for pos := 0; pos < len(output); { next := bytes.IndexByte(output[pos:], '\n') if next != -1 { @@ -246,7 +246,7 @@ func ContainsCrash(output []byte) bool { next = len(output) } for _, oops := range oopses { - match := matchOops(output[pos:next], oops) + match := matchOops(output[pos:next], oops, ignores) if match == -1 { continue } @@ -261,7 +261,7 @@ func ContainsCrash(output []byte) bool { // Desc contains a representative description of the first oops (empty if no oops found), // text contains whole oops text, // start and end denote region of output with oops message(s). -func Parse(output []byte) (desc string, text []byte, start int, end int) { +func Parse(output []byte, ignores []*regexp.Regexp) (desc string, text []byte, start int, end int) { var oops *oops for pos := 0; pos < len(output); { next := bytes.IndexByte(output[pos:], '\n') @@ -271,7 +271,7 @@ func Parse(output []byte) (desc string, text []byte, start int, end int) { next = len(output) } for _, oops1 := range oopses { - match := matchOops(output[pos:next], oops1) + match := matchOops(output[pos:next], oops1, ignores) if match == -1 { continue } @@ -306,7 +306,7 @@ func Parse(output []byte) (desc string, text []byte, start int, end int) { return } -func matchOops(line []byte, oops *oops) int { +func matchOops(line []byte, oops *oops, ignores []*regexp.Regexp) int { match := bytes.Index(line, oops.header) if match == -1 { return -1 @@ -316,6 +316,11 @@ func matchOops(line []byte, oops *oops) int { return -1 } } + for _, ignore := range ignores { + if ignore.Match(line) { + return -1 + } + } return match } diff --git a/report/report_test.go b/report/report_test.go index f6fdd7597..b75c23a83 100644 --- a/report/report_test.go +++ b/report/report_test.go @@ -5,6 +5,7 @@ package report import ( "fmt" + "regexp" "strings" "testing" @@ -500,7 +501,7 @@ WARNING: /etc/ssh/moduli does not exist, using fixed modulus tests[strings.Replace(log, "\n", "\r\n", -1)] = crash } for log, crash := range tests { - containsCrash := ContainsCrash([]byte(log)) + containsCrash := ContainsCrash([]byte(log), nil) expectCrash := (crash != "") if expectCrash && !containsCrash { t.Fatalf("ContainsCrash did not find crash") @@ -508,7 +509,7 @@ WARNING: /etc/ssh/moduli does not exist, using fixed modulus if !expectCrash && containsCrash { t.Fatalf("ContainsCrash found unexpected crash") } - desc, _, _, _ := Parse([]byte(log)) + desc, _, _, _ := Parse([]byte(log), nil) if desc == "" && crash != "" { t.Fatalf("did not find crash message '%v' in:\n%v", crash, log) } @@ -521,6 +522,52 @@ WARNING: /etc/ssh/moduli does not exist, using fixed modulus } } +func TestIgnores(t *testing.T) { + const log = ` + BUG: bug1 + BUG: bug2 + ` + if !ContainsCrash([]byte(log), nil) { + t.Fatalf("no crash") + } + if desc, _, _, _ := Parse([]byte(log), nil); desc != "BUG: bug1" { + t.Fatalf("want `BUG: bug1`, found `%v`", desc) + } + + ignores1 := []*regexp.Regexp{ + regexp.MustCompile("BUG: bug3"), + } + if !ContainsCrash([]byte(log), ignores1) { + t.Fatalf("no crash") + } + if desc, _, _, _ := Parse([]byte(log), ignores1); desc != "BUG: bug1" { + t.Fatalf("want `BUG: bug1`, found `%v`", desc) + } + + ignores2 := []*regexp.Regexp{ + regexp.MustCompile("BUG: bug3"), + regexp.MustCompile("BUG: bug1"), + } + if !ContainsCrash([]byte(log), ignores2) { + t.Fatalf("no crash") + } + if desc, _, _, _ := Parse([]byte(log), ignores2); desc != "BUG: bug2" { + t.Fatalf("want `BUG: bug2`, found `%v`", desc) + } + + ignores3 := []*regexp.Regexp{ + regexp.MustCompile("BUG: bug3"), + regexp.MustCompile("BUG: bug1"), + regexp.MustCompile("BUG: bug2"), + } + if ContainsCrash([]byte(log), ignores3) { + t.Fatalf("found crash, should be ignored") + } + if desc, _, _, _ := Parse([]byte(log), ignores3); desc != "" { + t.Fatalf("found `%v`, should be ignored", desc) + } +} + func TestReplace(t *testing.T) { tests := []struct { where string diff --git a/repro/repro.go b/repro/repro.go index 0b797a084..f6ec8df1e 100644 --- a/repro/repro.go +++ b/repro/repro.go @@ -51,7 +51,7 @@ func Run(crashLog []byte, cfg *config.Config, vmIndexes []int) (*Result, error) if len(entries) == 0 { return nil, fmt.Errorf("crash log does not contain any programs") } - crashDesc, _, crashStart, _ := report.Parse(crashLog) + crashDesc, _, crashStart, _ := report.Parse(crashLog, cfg.ParsedIgnores) if crashDesc == "" { crashStart = len(crashLog) // assuming VM hanged crashDesc = "hang" @@ -325,7 +325,7 @@ func (ctx *context) testImpl(inst vm.Instance, command string, duration time.Dur if err != nil { return false, fmt.Errorf("failed to run command in VM: %v", err) } - desc, text, output, crashed, timedout := vm.MonitorExecution(outc, errc, false, false) + desc, text, output, crashed, timedout := vm.MonitorExecution(outc, errc, false, false, ctx.cfg.ParsedIgnores) _, _, _ = text, output, timedout if !crashed { Logf(2, "reproducing crash '%v': program did not crash", ctx.crashDesc) diff --git a/syz-manager/manager.go b/syz-manager/manager.go index 4951172f2..666821ddf 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -14,7 +14,6 @@ import ( "os" "os/signal" "path/filepath" - "regexp" "sync" "syscall" "time" @@ -57,7 +56,6 @@ type Manager struct { mu sync.Mutex enabledSyscalls string enabledCalls []string // as determined by fuzzer - suppressions []*regexp.Regexp candidates [][]byte // untriaged inputs disabledHashes []string @@ -85,7 +83,7 @@ type Crash struct { func main() { flag.Parse() EnableLogCaching(1000, 1<<20) - cfg, syscalls, suppressions, err := config.Parse(*flagConfig) + cfg, syscalls, err := config.Parse(*flagConfig) if err != nil { Fatalf("%v", err) } @@ -94,10 +92,10 @@ func main() { cfg.Count = 1 } initAllCover(cfg.Vmlinux) - RunManager(cfg, syscalls, suppressions) + RunManager(cfg, syscalls) } -func RunManager(cfg *config.Config, syscalls map[int]bool, suppressions []*regexp.Regexp) { +func RunManager(cfg *config.Config, syscalls map[int]bool) { crashdir := filepath.Join(cfg.Workdir, "crashes") os.MkdirAll(crashdir, 0700) @@ -117,7 +115,6 @@ func RunManager(cfg *config.Config, syscalls map[int]bool, suppressions []*regex startTime: time.Now(), stats: make(map[string]uint64), enabledSyscalls: enabledSyscalls, - suppressions: suppressions, corpusCover: make([]cover.Cover, sys.CallCount), fuzzers: make(map[string]*Fuzzer), fresh: true, @@ -381,7 +378,7 @@ func (mgr *Manager) runInstance(vmCfg *vm.Config, first bool) (*Crash, error) { return nil, fmt.Errorf("failed to run fuzzer: %v", err) } - desc, text, output, crashed, timedout := vm.MonitorExecution(outc, errc, mgr.cfg.Type == "local", true) + desc, text, output, crashed, timedout := vm.MonitorExecution(outc, errc, mgr.cfg.Type == "local", true, mgr.cfg.ParsedIgnores) if timedout { // This is the only "OK" outcome. Logf(0, "%v: running for %v, restarting (%v)", vmCfg.Name, time.Since(start), desc) @@ -395,7 +392,7 @@ func (mgr *Manager) runInstance(vmCfg *vm.Config, first bool) (*Crash, error) { } func (mgr *Manager) isSuppressed(crash *Crash) bool { - for _, re := range mgr.suppressions { + for _, re := range mgr.cfg.ParsedSuppressions { if !re.Match(crash.output) { continue } diff --git a/tools/syz-crush/crush.go b/tools/syz-crush/crush.go index 32b4a715b..d519107f9 100644 --- a/tools/syz-crush/crush.go +++ b/tools/syz-crush/crush.go @@ -33,7 +33,7 @@ var ( func main() { flag.Parse() - cfg, _, _, err := config.Parse(*flagConfig) + cfg, _, err := config.Parse(*flagConfig) if err != nil { Fatalf("%v", err) } @@ -113,7 +113,7 @@ func runInstance(cfg *config.Config, vmCfg *vm.Config) { } Logf(0, "%v: crushing...", vmCfg.Name) - desc, _, output, crashed, timedout := vm.MonitorExecution(outc, errc, cfg.Type == "local", true) + desc, _, output, crashed, timedout := vm.MonitorExecution(outc, errc, cfg.Type == "local", true, cfg.ParsedIgnores) if timedout { // This is the only "OK" outcome. Logf(0, "%v: running long enough, restarting", vmCfg.Name) diff --git a/tools/syz-report/syz-report.go b/tools/syz-report/syz-report.go index aed958b51..eff8d6b8c 100644 --- a/tools/syz-report/syz-report.go +++ b/tools/syz-report/syz-report.go @@ -21,7 +21,7 @@ func main() { fmt.Fprintf(os.Stderr, "failed to read report file: %v\n", err) os.Exit(1) } - desc, text, _, _ := report.Parse(output) + desc, text, _, _ := report.Parse(output, nil) if desc == "" { fmt.Fprintf(os.Stderr, "report file does not contain a crash\n") os.Exit(1) diff --git a/tools/syz-repro/repro.go b/tools/syz-repro/repro.go index fb9ad0ef6..e2e2f6985 100644 --- a/tools/syz-repro/repro.go +++ b/tools/syz-repro/repro.go @@ -30,7 +30,7 @@ var ( func main() { os.Args = append(append([]string{}, os.Args[0], "-v=10"), os.Args[1:]...) flag.Parse() - cfg, _, _, err := config.Parse(*flagConfig) + cfg, _, err := config.Parse(*flagConfig) if err != nil { Fatalf("%v", err) } diff --git a/tools/syz-symbolize/symbolize.go b/tools/syz-symbolize/symbolize.go index df4f4765f..4e5e8564b 100644 --- a/tools/syz-symbolize/symbolize.go +++ b/tools/syz-symbolize/symbolize.go @@ -29,7 +29,7 @@ func main() { fmt.Fprintf(os.Stderr, "failed to open input file: %v\n", err) os.Exit(1) } - if _, parsed, _, _ := report.Parse(text); len(parsed) != 0 { + if _, parsed, _, _ := report.Parse(text, nil); len(parsed) != 0 { text = parsed } text, err = report.Symbolize(filepath.Join(*flagLinux, "vmlinux"), text) @@ -9,6 +9,7 @@ import ( "fmt" "io" "os" + "regexp" "syscall" "time" @@ -86,7 +87,7 @@ func LongPipe() (io.ReadCloser, io.WriteCloser, error) { var TimeoutErr = errors.New("timeout") -func MonitorExecution(outc <-chan []byte, errc <-chan error, local, needOutput bool) (desc string, text, output []byte, crashed, timedout bool) { +func MonitorExecution(outc <-chan []byte, errc <-chan error, local, needOutput bool, ignores []*regexp.Regexp) (desc string, text, output []byte, crashed, timedout bool) { waitForOutput := func() { dur := time.Second if needOutput { @@ -117,10 +118,10 @@ func MonitorExecution(outc <-chan []byte, errc <-chan error, local, needOutput b if bytes.Contains(output, []byte("SYZ-FUZZER: PREEMPTED")) { return "preempted", nil, nil, false, true } - if !report.ContainsCrash(output[matchPos:]) { + if !report.ContainsCrash(output[matchPos:], ignores) { return defaultError, nil, output, true, false } - desc, text, start, end := report.Parse(output[matchPos:]) + desc, text, start, end := report.Parse(output[matchPos:], ignores) start = start + matchPos - beforeContext if start < 0 { start = 0 @@ -162,7 +163,7 @@ func MonitorExecution(outc <-chan []byte, errc <-chan error, local, needOutput b if bytes.Index(output[matchPos:], []byte("executed programs:")) != -1 { // syz-execprog output lastExecuteTime = time.Now() } - if report.ContainsCrash(output[matchPos:]) { + if report.ContainsCrash(output[matchPos:], ignores) { return extractError("") } if len(output) > 2*beforeContext { |
