aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config/config.go82
-rw-r--r--config/config_test.go2
-rw-r--r--report/report.go15
-rw-r--r--report/report_test.go51
-rw-r--r--repro/repro.go4
-rw-r--r--syz-manager/manager.go13
-rw-r--r--tools/syz-crush/crush.go4
-rw-r--r--tools/syz-report/syz-report.go2
-rw-r--r--tools/syz-repro/repro.go2
-rw-r--r--tools/syz-symbolize/symbolize.go2
-rw-r--r--vm/vm.go9
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)
diff --git a/vm/vm.go b/vm/vm.go
index d42ddd4cd..ce4138344 100644
--- a/vm/vm.go
+++ b/vm/vm.go
@@ -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 {