diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2017-05-29 14:33:50 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-05-29 14:33:50 +0200 |
| commit | baf825803c72cdc02bed92a5f84ec43482aa35d3 (patch) | |
| tree | c9594f9450bb91ce03a5f671f53755c193077ef3 | |
| parent | 145e067777cb0d21644412548e67dcb934f1da5e (diff) | |
| parent | eaf1f711fc42235d0b9a73c6877d14b1b5244194 (diff) | |
Merge pull request #196 from dvyukov/executor-fault-inject3
fault injection and faster tests
| -rw-r--r-- | Makefile | 26 | ||||
| -rw-r--r-- | csource/common.go | 33 | ||||
| -rw-r--r-- | csource/csource.go | 27 | ||||
| -rw-r--r-- | csource/csource_test.go | 24 | ||||
| -rw-r--r-- | executor/common.h | 33 | ||||
| -rw-r--r-- | executor/executor.cc | 48 | ||||
| -rw-r--r-- | host/host_test.go | 2 | ||||
| -rw-r--r-- | ipc/ipc.go | 75 | ||||
| -rw-r--r-- | ipc/ipc_test.go | 12 | ||||
| -rw-r--r-- | prog/parse.go | 40 | ||||
| -rw-r--r-- | prog/parse_test.go | 34 | ||||
| -rw-r--r-- | prog/rand.go | 4 | ||||
| -rw-r--r-- | report/report.go | 24 | ||||
| -rw-r--r-- | report/report_test.go | 12 | ||||
| -rw-r--r-- | repro/repro.go | 23 | ||||
| -rw-r--r-- | rpctype/rpctype.go | 2 | ||||
| -rw-r--r-- | symbolizer/nm_test.go | 44 | ||||
| -rwxr-xr-x | symbolizer/testdata/nm.test.out | bin | 0 -> 8578 bytes | |||
| -rw-r--r-- | sys/align.go | 2 | ||||
| -rw-r--r-- | sys/decl.go | 110 | ||||
| -rw-r--r-- | sys/decl_test.go | 2 | ||||
| -rw-r--r-- | sysgen/sysgen.go | 125 | ||||
| -rw-r--r-- | sysparser/lexer.go | 6 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer.go | 68 | ||||
| -rw-r--r-- | syz-manager/manager.go | 3 | ||||
| -rw-r--r-- | tools/syz-execprog/execprog.go | 22 | ||||
| -rw-r--r-- | tools/syz-prog2c/prog2c.go | 29 | ||||
| -rw-r--r-- | tools/syz-stress/stress.go | 4 | ||||
| -rw-r--r-- | vm/merger.go | 5 |
29 files changed, 648 insertions, 191 deletions
@@ -21,39 +21,45 @@ all-tools: execprog mutate prog2c stress repro upgrade executor: $(CC) -o ./bin/syz-executor executor/executor.cc -pthread -Wall -O1 -g $(STATIC_FLAG) $(CFLAGS) +# Don't generate symbol table and DWARF debug info. +# Reduces build time and binary sizes considerably. +# That's only needed if you use gdb or nm. +# If you need that, build manually without these flags. +GOFLAGS="-ldflags=-s -w" + manager: - go build -o ./bin/syz-manager github.com/google/syzkaller/syz-manager + go build $(GOFLAGS) -o ./bin/syz-manager github.com/google/syzkaller/syz-manager fuzzer: - go build -o ./bin/syz-fuzzer github.com/google/syzkaller/syz-fuzzer + go build $(GOFLAGS) -o ./bin/syz-fuzzer github.com/google/syzkaller/syz-fuzzer execprog: - go build -o ./bin/syz-execprog github.com/google/syzkaller/tools/syz-execprog + go build $(GOFLAGS) -o ./bin/syz-execprog github.com/google/syzkaller/tools/syz-execprog repro: - go build -o ./bin/syz-repro github.com/google/syzkaller/tools/syz-repro + go build $(GOFLAGS) -o ./bin/syz-repro github.com/google/syzkaller/tools/syz-repro mutate: - go build -o ./bin/syz-mutate github.com/google/syzkaller/tools/syz-mutate + go build $(GOFLAGS) -o ./bin/syz-mutate github.com/google/syzkaller/tools/syz-mutate prog2c: - go build -o ./bin/syz-prog2c github.com/google/syzkaller/tools/syz-prog2c + go build $(GOFLAGS) -o ./bin/syz-prog2c github.com/google/syzkaller/tools/syz-prog2c stress: - go build -o ./bin/syz-stress github.com/google/syzkaller/tools/syz-stress + go build $(GOFLAGS) -o ./bin/syz-stress github.com/google/syzkaller/tools/syz-stress upgrade: - go build -o ./bin/syz-upgrade github.com/google/syzkaller/tools/syz-upgrade + go build $(GOFLAGS) -o ./bin/syz-upgrade github.com/google/syzkaller/tools/syz-upgrade extract: bin/syz-extract LINUX=$(LINUX) LINUXBLD=$(LINUXBLD) ./extract.sh bin/syz-extract: syz-extract/*.go sysparser/*.go - go build -o $@ ./syz-extract + go build $(GOFLAGS) -o $@ ./syz-extract generate: bin/syz-sysgen bin/syz-sysgen bin/syz-sysgen: sysgen/*.go sysparser/*.go - go build -o $@ ./sysgen + go build $(GOFLAGS) -o $@ ./sysgen format: go fmt ./... diff --git a/csource/common.go b/csource/common.go index 240dff6c8..25d062821 100644 --- a/csource/common.go +++ b/csource/common.go @@ -1602,13 +1602,7 @@ static int do_sandbox_setuid(int executor_pid, bool enable_tun) } #endif -#if defined(SYZ_EXECUTOR) || defined(SYZ_SANDBOX_NAMESPACE) -static int real_uid; -static int real_gid; -static int epid; -static bool etun; -__attribute__((aligned(64 << 10))) static char sandbox_stack[1 << 20]; - +#if defined(SYZ_EXECUTOR) || defined(SYZ_SANDBOX_NAMESPACE) || defined(SYZ_FAULT_INJECTION) static bool write_file(const char* file, const char* what, ...) { char buf[1024]; @@ -1629,6 +1623,14 @@ static bool write_file(const char* file, const char* what, ...) close(fd); return true; } +#endif + +#if defined(SYZ_EXECUTOR) || defined(SYZ_SANDBOX_NAMESPACE) +static int real_uid; +static int real_gid; +static int epid; +static bool etun; +__attribute__((aligned(64 << 10))) static char sandbox_stack[1 << 20]; static int namespace_sandbox_proc(void* arg) { @@ -1785,6 +1787,23 @@ static uint64_t current_time_ms() } #endif +#if defined(SYZ_EXECUTOR) || defined(SYZ_FAULT_INJECTION) +static int inject_fault(int nth) +{ + int fd; + char buf[128]; + + sprintf(buf, "/proc/self/task/%d/fail-nth", (int)syscall(SYS_gettid)); + fd = open(buf, O_RDWR); + if (fd == -1) + fail("failed to open /proc/self/task/tid/fail-nth"); + sprintf(buf, "%d", nth + 1); + if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) + fail("failed to write /proc/self/task/tid/fail-nth"); + return fd; +} +#endif + #if defined(SYZ_REPEAT) static void test(); diff --git a/csource/csource.go b/csource/csource.go index 959538dbb..ab585ebec 100644 --- a/csource/csource.go +++ b/csource/csource.go @@ -22,12 +22,15 @@ import ( ) type Options struct { - Threaded bool - Collide bool - Repeat bool - Procs int - Sandbox string - Repro bool // generate code for use with repro package + Threaded bool + Collide bool + Repeat bool + Procs int + Sandbox string + Fault bool // inject fault into FaultCall/FaultNth + FaultCall int + FaultNth int + Repro bool // generate code for use with repro package } func Write(p *prog.Prog, opts Options) ([]byte, error) { @@ -65,7 +68,7 @@ func Write(p *prog.Prog, opts Options) ([]byte, error) { fmt.Fprint(w, hdr) fmt.Fprint(w, "\n") - calls, nvar := generateCalls(exec) + calls, nvar := generateCalls(exec, opts) fmt.Fprintf(w, "long r[%v];\n", nvar) if !opts.Repeat { @@ -161,7 +164,7 @@ func generateTestFunc(w io.Writer, opts Options, calls []string, name string) { } } -func generateCalls(exec []byte) ([]string, int) { +func generateCalls(exec []byte, opts Options) ([]string, int) { read := func() uintptr { if len(exec) < 8 { panic("exec program overflow") @@ -265,6 +268,11 @@ loop: default: // Normal syscall. newCall() + if opts.Fault && opts.FaultCall == len(calls) { + fmt.Fprintf(w, "\twrite_file(\"/sys/kernel/debug/failslab/ignore-gfp-wait\", \"N\");\n") + fmt.Fprintf(w, "\twrite_file(\"/sys/kernel/debug/fail_futex/ignore-private\", \"N\");\n") + fmt.Fprintf(w, "\tinject_fault(%v);\n", opts.FaultNth) + } meta := sys.Calls[instr] fmt.Fprintf(w, "\tr[%v] = execute_syscall(__NR_%v", n, meta.CallName) nargs := read() @@ -311,6 +319,9 @@ func preprocessCommonHeader(opts Options, handled map[string]int) (string, error if opts.Repeat { defines = append(defines, "SYZ_REPEAT") } + if opts.Fault { + defines = append(defines, "SYZ_FAULT_INJECTION") + } for name, _ := range handled { defines = append(defines, "__NR_"+name) } diff --git a/csource/csource_test.go b/csource/csource_test.go index 259b5689b..78998a32f 100644 --- a/csource/csource_test.go +++ b/csource/csource_test.go @@ -15,6 +15,7 @@ import ( ) func initTest(t *testing.T) (rand.Source, int) { + t.Parallel() iters := 10 if testing.Short() { iters = 1 @@ -34,16 +35,18 @@ func allOptionsPermutations() []Options { for _, opt.Repro = range []bool{false, true} { for _, opt.Procs = range []int{1, 4} { for _, opt.Sandbox = range []string{"none", "setuid", "namespace"} { - if opt.Collide && !opt.Threaded { - continue + for _, opt.Fault = range []bool{false, true} { + if opt.Collide && !opt.Threaded { + continue + } + if !opt.Repeat && opt.Procs != 1 { + continue + } + if testing.Short() && opt.Procs != 1 { + continue + } + options = append(options, opt) } - if !opt.Repeat && opt.Procs != 1 { - continue - } - if testing.Short() && opt.Procs != 1 { - continue - } - options = append(options, opt) } } } @@ -68,11 +71,12 @@ func TestSyz(t *testing.T) { } func Test(t *testing.T) { - rs, iters := initTest(t) + rs, _ := initTest(t) syzProg := prog.GenerateAllSyzProg(rs) t.Logf("syz program:\n%s\n", syzProg.Serialize()) for i, opts := range allOptionsPermutations() { t.Run(fmt.Sprintf("%v", i), func(t *testing.T) { + rs, iters := initTest(t) t.Logf("opts: %+v", opts) for i := 0; i < iters; i++ { p := prog.Generate(rs, 10, nil) diff --git a/executor/common.h b/executor/common.h index c32341f57..73a528471 100644 --- a/executor/common.h +++ b/executor/common.h @@ -699,13 +699,7 @@ static int do_sandbox_setuid(int executor_pid, bool enable_tun) } #endif -#if defined(SYZ_EXECUTOR) || defined(SYZ_SANDBOX_NAMESPACE) -static int real_uid; -static int real_gid; -static int epid; -static bool etun; -__attribute__((aligned(64 << 10))) static char sandbox_stack[1 << 20]; - +#if defined(SYZ_EXECUTOR) || defined(SYZ_SANDBOX_NAMESPACE) || defined(SYZ_FAULT_INJECTION) static bool write_file(const char* file, const char* what, ...) { char buf[1024]; @@ -726,6 +720,14 @@ static bool write_file(const char* file, const char* what, ...) close(fd); return true; } +#endif + +#if defined(SYZ_EXECUTOR) || defined(SYZ_SANDBOX_NAMESPACE) +static int real_uid; +static int real_gid; +static int epid; +static bool etun; +__attribute__((aligned(64 << 10))) static char sandbox_stack[1 << 20]; static int namespace_sandbox_proc(void* arg) { @@ -897,6 +899,23 @@ static uint64_t current_time_ms() } #endif +#if defined(SYZ_EXECUTOR) || defined(SYZ_FAULT_INJECTION) +static int inject_fault(int nth) +{ + int fd; + char buf[128]; + + sprintf(buf, "/proc/self/task/%d/fail-nth", (int)syscall(SYS_gettid)); + fd = open(buf, O_RDWR); + if (fd == -1) + fail("failed to open /proc/self/task/tid/fail-nth"); + sprintf(buf, "%d", nth + 1); + if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) + fail("failed to write /proc/self/task/tid/fail-nth"); + return fd; +} +#endif + #if defined(SYZ_REPEAT) static void test(); diff --git a/executor/executor.cc b/executor/executor.cc index 02792f82f..cb31e0697 100644 --- a/executor/executor.cc +++ b/executor/executor.cc @@ -76,9 +76,14 @@ bool flag_collide; bool flag_sandbox_privs; sandbox_type flag_sandbox; bool flag_enable_tun; +bool flag_enable_fault_injection; bool flag_collect_cover; bool flag_dedup_cover; +// Inject fault into flag_fault_nth-th operation in flag_fault_call-th syscall. +bool flag_inject_fault; +int flag_fault_call; +int flag_fault_nth; __attribute__((aligned(64 << 10))) char input_data[kMaxInput]; uint32_t* output_data; @@ -111,6 +116,7 @@ struct thread_t { uint64_t res; uint64_t reserrno; uint64_t cover_size; + bool fault_injected; int cover_fd; }; @@ -135,7 +141,6 @@ void execute_call(thread_t* th); void handle_completion(thread_t* th); void thread_create(thread_t* th, int id); void* worker_thread(void* arg); -bool write_file(const char* file, const char* what, ...); void cover_open(); void cover_enable(thread_t* th); void cover_reset(thread_t* th); @@ -176,8 +181,9 @@ int main(int argc, char** argv) if (!flag_threaded) flag_collide = false; flag_enable_tun = flags & (1 << 6); - uint64_t executor_pid = *((uint64_t*)input_data + 1); + flag_enable_fault_injection = flags & (1 << 7); + uint64_t executor_pid = *((uint64_t*)input_data + 1); cover_open(); setup_main_process(); @@ -240,11 +246,16 @@ void loop() // TODO: consider moving the read into the child. // Potentially it can speed up things a bit -- when the read finishes // we already have a forked worker process. - char flags = 0; - if (read(kInPipeFd, &flags, 1) != 1) + uint64_t in_cmd[3] = {}; + if (read(kInPipeFd, &in_cmd[0], sizeof(in_cmd)) != (ssize_t)sizeof(in_cmd)) fail("control pipe read failed"); - flag_collect_cover = flags & (1 << 0); - flag_dedup_cover = flags & (1 << 1); + flag_collect_cover = in_cmd[0] & (1 << 0); + flag_dedup_cover = in_cmd[0] & (1 << 1); + flag_inject_fault = in_cmd[0] & (1 << 2); + flag_fault_call = in_cmd[1]; + flag_fault_nth = in_cmd[2]; + debug("exec opts: cover=%d dedup=%d fault=%d/%d/%d\n", flag_collect_cover, flag_dedup_cover, + flag_inject_fault, flag_fault_call, flag_fault_nth); int pid = fork(); if (pid < 0) @@ -482,7 +493,7 @@ retry: } } - if (flag_collide && !collide) { + if (flag_collide && !flag_inject_fault && !collide) { debug("enabling collider\n"); collide = true; goto retry; @@ -561,6 +572,7 @@ void handle_completion(thread_t* th) write_output(th->call_num); uint32_t reserrno = th->res != (uint64_t)-1 ? 0 : th->reserrno; write_output(reserrno); + write_output(th->fault_injected); uint32_t* signal_count_pos = write_output(0); // filled in later uint32_t* cover_count_pos = write_output(0); // filled in later @@ -651,10 +663,32 @@ void execute_call(thread_t* th) } debug(")\n"); + int fail_fd = -1; + if (flag_inject_fault && th->call_index == flag_fault_call) { + if (collide) + fail("both collide and fault injection are enabled"); + debug("injecting fault into %d-th operation\n", flag_fault_nth); + fail_fd = inject_fault(flag_fault_nth); + } + cover_reset(th); th->res = execute_syscall(call->sys_nr, th->args[0], th->args[1], th->args[2], th->args[3], th->args[4], th->args[5], th->args[6], th->args[7], th->args[8]); th->reserrno = errno; th->cover_size = cover_read(th); + th->fault_injected = false; + + if (flag_inject_fault && th->call_index == flag_fault_call) { + char buf[16]; + int n = read(fail_fd, buf, sizeof(buf) - 1); + if (n <= 0) + fail("failed to read /proc/self/task/tid/fail-nth"); + th->fault_injected = n == 2 && buf[0] == '0' && buf[1] == '\n'; + buf[0] = '0'; + if (write(fail_fd, buf, 1) != 1) + fail("failed to write /proc/self/task/tid/fail-nth"); + close(fail_fd); + debug("fault injected: %d\n", th->fault_injected); + } if (th->res == (uint64_t)-1) debug("#%d: %s = errno(%ld)\n", th->id, call->name, th->reserrno); diff --git a/host/host_test.go b/host/host_test.go index 7f58b1cd0..9b44d8e36 100644 --- a/host/host_test.go +++ b/host/host_test.go @@ -11,6 +11,7 @@ import ( ) func TestLog(t *testing.T) { + t.Parallel() // Dump for manual inspection. supp, err := DetectSupportedSyscalls() if err != nil { @@ -40,6 +41,7 @@ func TestLog(t *testing.T) { } func TestSupportedSyscalls(t *testing.T) { + t.Parallel() supp, err := DetectSupportedSyscalls() if err != nil { t.Skipf("skipping: %v", err) diff --git a/ipc/ipc.go b/ipc/ipc.go index 377beddda..ce951aab6 100644 --- a/ipc/ipc.go +++ b/ipc/ipc.go @@ -12,6 +12,7 @@ import ( "os/exec" "path/filepath" "strings" + "sync" "sync/atomic" "syscall" "time" @@ -45,6 +46,14 @@ const ( FlagSandboxSetuid // impersonate nobody user FlagSandboxNamespace // use namespaces for sandboxing FlagEnableTun // initialize and use tun in executor + FlagEnableFault // enable fault injection support +) + +// Per-exec flags for ExecOpts.Flags: +const ( + FlagCollectCover = uint64(1) << iota // collect coverage + FlagDedupCover // deduplicate coverage in executor + FlagInjectFault // inject a fault in this execution (see ExecOpts) ) const ( @@ -70,6 +79,12 @@ var ( flagBufferSize = flag.Uint64("buffer_size", 0, "internal buffer size (in bytes) for executor output") ) +type ExecOpts struct { + Flags uint64 + FaultCall int // call index for fault injection (0-based) + FaultNth int // fault n-th operation in the call (0-based) +} + // ExecutorFailure is returned from MakeEnv or from env.Exec when executor terminates by calling fail function. // This is considered a logical error (a failed assert). type ExecutorFailure string @@ -147,10 +162,8 @@ func MakeEnv(bin string, pid int, config Config) (*Env, error) { closeMapping(outf, outmem) } }() - for i := 0; i < 8; i++ { - inmem[i] = byte(config.Flags >> (8 * uint(i))) - } - *(*uint64)(unsafe.Pointer(&inmem[8])) = uint64(pid) + serializeUint64(inmem[0:], config.Flags) + serializeUint64(inmem[8:], uint64(pid)) inmem = inmem[16:] env := &Env{ In: inmem, @@ -208,7 +221,8 @@ type CallInfo struct { Signal []uint32 // feedback signal, filled if FlagSignal is set Cover []uint32 // per-call coverage, filled if FlagSignal is set and cover == true, //if dedup == false, then cov effectively contains a trace, otherwise duplicates are removed - Errno int // call errno (0 if the call was successful) + Errno int // call errno (0 if the call was successful) + FaultInjected bool } // Exec starts executor binary to execute program p and returns information about the execution: @@ -217,7 +231,7 @@ type CallInfo struct { // failed: true if executor has detected a kernel bug // hanged: program hanged and was killed // err0: failed to start process, or executor has detected a logical error -func (env *Env) Exec(p *prog.Prog, cover, dedup bool) (output []byte, info []CallInfo, failed, hanged bool, err0 error) { +func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info []CallInfo, failed, hanged bool, err0 error) { if p != nil { // Copy-in serialized program. if err := p.SerializeForExec(env.In, env.pid); err != nil { @@ -242,7 +256,7 @@ func (env *Env) Exec(p *prog.Prog, cover, dedup bool) (output []byte, info []Cal } } var restart bool - output, failed, hanged, restart, err0 = env.cmd.exec(cover, dedup) + output, failed, hanged, restart, err0 = env.cmd.exec(opts) if err0 != nil || restart { env.cmd.close() env.cmd = nil @@ -288,8 +302,8 @@ func (env *Env) readOutCoverage(p *prog.Prog) (info []CallInfo, err0 error) { return buf.String() } for i := uint32(0); i < ncmd; i++ { - var callIndex, callNum, errno, signalSize, coverSize uint32 - if !readOut(&callIndex) || !readOut(&callNum) || !readOut(&errno) || !readOut(&signalSize) || !readOut(&coverSize) { + var callIndex, callNum, errno, faultInjected, signalSize, coverSize uint32 + if !readOut(&callIndex) || !readOut(&callNum) || !readOut(&errno) || !readOut(&faultInjected) || !readOut(&signalSize) || !readOut(&coverSize) { err0 = fmt.Errorf("executor %v: failed to read output coverage", env.pid) return } @@ -310,6 +324,7 @@ func (env *Env) readOutCoverage(p *prog.Prog) (info []CallInfo, err0 error) { return } info[callIndex].Errno = int(errno) + info[callIndex].FaultInjected = faultInjected != 0 if signalSize > uint32(len(out)) { err0 = fmt.Errorf("executor %v: failed to read output signal: record %v, call %v, signalsize=%v coversize=%v", env.pid, i, callIndex, signalSize, coverSize) @@ -566,15 +581,15 @@ func (c *command) wait() error { return err } -func (c *command) exec(cover, dedup bool) (output []byte, failed, hanged, restart bool, err0 error) { - var flags [1]byte - if cover { - flags[0] |= 1 << 0 - if dedup { - flags[0] |= 1 << 1 - } +func (c *command) exec(opts *ExecOpts) (output []byte, failed, hanged, restart bool, err0 error) { + if opts.Flags&FlagInjectFault != 0 { + enableFaultOnce.Do(enableFaultInjection) } - if _, err := c.outwp.Write(flags[:]); err != nil { + var inCmd [24]byte + serializeUint64(inCmd[0:], opts.Flags) + serializeUint64(inCmd[8:], uint64(opts.FaultCall)) + serializeUint64(inCmd[16:], uint64(opts.FaultNth)) + if _, err := c.outwp.Write(inCmd[:]); err != nil { output = <-c.readDone err0 = fmt.Errorf("failed to write control pipe: %v", err) return @@ -592,21 +607,22 @@ func (c *command) exec(cover, dedup bool) (output []byte, failed, hanged, restar hang <- false } }() - readN, readErr := c.inrp.Read(flags[:]) + var reply [1]byte + readN, readErr := c.inrp.Read(reply[:]) close(done) status := 0 if readErr == nil { - if readN != len(flags) { + if readN != len(reply) { panic(fmt.Sprintf("executor %v: read only %v bytes", c.pid, readN)) } - status = int(flags[0]) + status = int(reply[0]) if status == 0 { <-hang return } // Executor writes magic values into the pipe before exiting, // so proceed with killing and joining it. - status = int(flags[0]) + status = int(reply[0]) } err0 = fmt.Errorf("executor did not answer") c.abort() @@ -643,3 +659,20 @@ func (c *command) exec(cover, dedup bool) (output []byte, failed, hanged, restar } return } + +func serializeUint64(buf []byte, v uint64) { + for i := 0; i < 8; i++ { + buf[i] = byte(v >> (8 * uint(i))) + } +} + +var enableFaultOnce sync.Once + +func enableFaultInjection() { + if err := ioutil.WriteFile("/sys/kernel/debug/failslab/ignore-gfp-wait", []byte("N"), 0600); err != nil { + panic(fmt.Sprintf("failed to write /sys/kernel/debug/failslab/ignore-gfp-wait: %v", err)) + } + if err := ioutil.WriteFile("/sys/kernel/debug/fail_futex/ignore-private", []byte("N"), 0600); err != nil { + panic(fmt.Sprintf("failed to write /sys/kernel/debug/fail_futex/ignore-private: %v", err)) + } +} diff --git a/ipc/ipc_test.go b/ipc/ipc_test.go index bcf453966..726da8485 100644 --- a/ipc/ipc_test.go +++ b/ipc/ipc_test.go @@ -38,6 +38,7 @@ func buildProgram(t *testing.T, src string) string { } func initTest(t *testing.T) (rand.Source, int) { + t.Parallel() iters := 100 if testing.Short() { iters = 10 @@ -62,7 +63,8 @@ func TestEmptyProg(t *testing.T) { defer env.Close() p := new(prog.Prog) - output, _, failed, hanged, err := env.Exec(p, false, false) + opts := &ExecOpts{} + output, _, failed, hanged, err := env.Exec(opts, p) if err != nil { t.Fatalf("failed to run executor: %v", err) } @@ -75,11 +77,12 @@ func TestEmptyProg(t *testing.T) { } func TestExecute(t *testing.T) { + rs, iters := initTest(t) + flags := []uint64{0, FlagThreaded, FlagThreaded | FlagCollide} + bin := buildExecutor(t) defer os.Remove(bin) - rs, iters := initTest(t) - flags := []uint64{0, FlagThreaded, FlagThreaded | FlagCollide} for _, flag := range flags { t.Logf("testing flags 0x%x\n", flag) cfg := Config{ @@ -94,7 +97,8 @@ func TestExecute(t *testing.T) { for i := 0; i < iters/len(flags); i++ { p := prog.Generate(rs, 10, nil) - output, _, _, _, err := env.Exec(p, false, false) + opts := &ExecOpts{} + output, _, _, _, err := env.Exec(opts, p) if err != nil { t.Logf("program:\n%s\n", p.Serialize()) t.Fatalf("failed to run executor: %v\n%s", err, output) diff --git a/prog/parse.go b/prog/parse.go index e4a4e15ad..2044b1c9f 100644 --- a/prog/parse.go +++ b/prog/parse.go @@ -10,10 +10,13 @@ import ( // LogEntry describes one program in execution log. type LogEntry struct { - P *Prog - Proc int // index of parallel proc - Start int // start offset in log - End int // end offset in log + P *Prog + Proc int // index of parallel proc + Start int // start offset in log + End int // end offset in log + Fault bool // program was executed with fault injection in FaultCall/FaultNth + FaultCall int + FaultNth int } func ParseLog(data []byte) []*LogEntry { @@ -30,22 +33,21 @@ func ParseLog(data []byte) []*LogEntry { line := data[pos : nl+1] pos0 := pos pos = nl + 1 - const delim = "executing program " - if delimPos := bytes.Index(line, []byte(delim)); delimPos != -1 { + + if proc, ok := extractInt(line, "executing program "); ok { if ent.P != nil && len(ent.P.Calls) != 0 { ent.End = pos0 entries = append(entries, ent) } - procStart := delimPos + len(delim) - procEnd := procStart - for procEnd != len(line) && line[procEnd] >= '0' && line[procEnd] <= '9' { - procEnd++ - } - proc, _ := strconv.Atoi(string(line[procStart:procEnd])) ent = &LogEntry{ Proc: proc, Start: pos0, } + if faultCall, ok := extractInt(line, "fault-call:"); ok { + ent.Fault = true + ent.FaultCall = faultCall + ent.FaultNth, _ = extractInt(line, "fault-nth:") + } cur = nil continue } @@ -66,3 +68,17 @@ func ParseLog(data []byte) []*LogEntry { } return entries } + +func extractInt(line []byte, prefix string) (int, bool) { + pos := bytes.Index(line, []byte(prefix)) + if pos == -1 { + return 0, false + } + pos += len(prefix) + end := pos + for end != len(line) && line[end] >= '0' && line[end] <= '9' { + end++ + } + v, _ := strconv.Atoi(string(line[pos:end])) + return v, true +} diff --git a/prog/parse_test.go b/prog/parse_test.go index 9492b997e..2f5dd75d6 100644 --- a/prog/parse_test.go +++ b/prog/parse_test.go @@ -25,6 +25,9 @@ gettid() if ent.Proc != 0 { t.Fatalf("proc %v, want 0", ent.Proc) } + if ent.Fault || ent.FaultCall != 0 || ent.FaultNth != 0 { + t.Fatalf("fault injection enabled") + } want := "getpid-gettid" got := ent.P.String() if got != want { @@ -53,6 +56,11 @@ func TestParseMulti(t *testing.T) { entries[4].Proc != 9 { t.Fatalf("bad procs") } + for i, ent := range entries { + if ent.Fault || ent.FaultCall != 0 || ent.FaultNth != 0 { + t.Fatalf("prog %v has fault injection enabled", i) + } + } if s := entries[0].P.String(); s != "getpid-gettid" { t.Fatalf("bad program 0: %s", s) } @@ -89,3 +97,29 @@ getpid() 2015/12/21 12:18:05 executing program 9: munlockall() ` + +func TestParseFault(t *testing.T) { + const execLog = `2015/12/21 12:18:05 executing program 1 (fault-call:1 fault-nth:55): +gettid() +getpid() +` + entries := ParseLog([]byte(execLog)) + if len(entries) != 1 { + t.Fatalf("got %v programs, want 1", len(entries)) + } + ent := entries[0] + if !ent.Fault { + t.Fatalf("fault injection is not enabled") + } + if ent.FaultCall != 1 { + t.Fatalf("fault call: got %v, want 1", ent.FaultCall) + } + if ent.FaultNth != 55 { + t.Fatalf("fault nth: got %v, want 55", ent.FaultNth) + } + want := "gettid-getpid" + got := ent.P.String() + if got != want { + t.Fatalf("bad program: %s, want %s", got, want) + } +} diff --git a/prog/rand.go b/prog/rand.go index 0d4596fd3..db493e1cd 100644 --- a/prog/rand.go +++ b/prog/rand.go @@ -382,8 +382,6 @@ func (r *randGen) randPageAddr(s *state, typ sys.Type, npages uintptr, data *Arg } starts = append(starts, i) } - *poolPtr = starts - pageStartPool.Put(poolPtr) var page uintptr if len(starts) != 0 { page = starts[r.rand(len(starts))] @@ -393,6 +391,8 @@ func (r *randGen) randPageAddr(s *state, typ sys.Type, npages uintptr, data *Arg if !vma { npages = 0 } + *poolPtr = starts + pageStartPool.Put(poolPtr) return pointerArg(typ, page, 0, npages, data) } diff --git a/report/report.go b/report/report.go index 0ade02e5d..b73f0d8d4 100644 --- a/report/report.go +++ b/report/report.go @@ -100,6 +100,30 @@ var oopses = []*oops{ compile("WARNING: .* at {{SRC}} {{FUNC}}"), "WARNING in %[2]v", }, + { + compile("WARNING: possible circular locking dependency detected(?:.*\\n)+?.*is trying to acquire lock(?:.*\\n)+?.*at: {{PC}} +{{FUNC}}"), + "possible deadlock in %[1]v", + }, + { + compile("WARNING: possible irq lock inversion dependency detected(?:.*\\n)+?.*just changed the state of lock(?:.*\\n)+?.*at: {{PC}} +{{FUNC}}"), + "possible deadlock in %[1]v", + }, + { + compile("WARNING: SOFTIRQ-safe -> SOFTIRQ-unsafe lock order detected(?:.*\\n)+?.*is trying to acquire(?:.*\\n)+?.*at: {{PC}} +{{FUNC}}"), + "possible deadlock in %[1]v", + }, + { + compile("WARNING: possible recursive locking detected(?:.*\\n)+?.*is trying to acquire lock(?:.*\\n)+?.*at: {{PC}} +{{FUNC}}"), + "possible deadlock in %[1]v", + }, + { + compile("WARNING: inconsistent lock state(?:.*\\n)+?.*takes(?:.*\\n)+?.*at: {{PC}} +{{FUNC}}"), + "inconsistent lock state in %[1]v", + }, + { + compile("WARNING: suspicious RCU usage(?:.*\n)+?.*?{{SRC}}"), + "suspicious RCU usage at %[1]v", + }, }, []*regexp.Regexp{ compile("WARNING: /etc/ssh/moduli does not exist, using fixed modulus"), // printed by sshd diff --git a/report/report_test.go b/report/report_test.go index 4d62613ed..61e24e03e 100644 --- a/report/report_test.go +++ b/report/report_test.go @@ -162,6 +162,18 @@ kacpi_hotplug/246 is trying to acquire lock: (kacpid){+.+.+.}, at: [<ffffffff8105bbd0>] flush_workqueue+0x0/0xb0 `: `possible deadlock in flush_workqueue`, + `WARNING: possible circular locking dependency detected +4.12.0-rc2-next-20170525+ #1 Not tainted +------------------------------------------------------ +kworker/u4:2/54 is trying to acquire lock: + (&buf->lock){+.+...}, at: [<ffffffff9edb41bb>] tty_buffer_flush+0xbb/0x3a0 drivers/tty/tty_buffer.c:221 + +but task is already holding lock: + (&o_tty->termios_rwsem/1){++++..}, at: [<ffffffff9eda4961>] isig+0xa1/0x4d0 drivers/tty/n_tty.c:1100 + +which lock already depends on the new lock. +`: `possible deadlock in tty_buffer_flush`, + ` [ 44.025025] ========================================================= [ 44.025025] [ INFO: possible irq lock inversion dependency detected ] diff --git a/repro/repro.go b/repro/repro.go index 3fbc385c4..b9124942e 100644 --- a/repro/repro.go +++ b/repro/repro.go @@ -171,6 +171,12 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er var duration time.Duration for _, dur := range []time.Duration{10 * time.Second, 5 * time.Minute} { for _, ent := range suspected { + opts.Fault = ent.Fault + opts.FaultCall = ent.FaultCall + opts.FaultNth = ent.FaultNth + if opts.FaultCall < 0 || opts.FaultCall >= len(ent.P.Calls) { + opts.FaultCall = len(ent.P.Calls) - 1 + } crashed, err := ctx.testProg(ent.P, dur, opts) if err != nil { return nil, err @@ -197,7 +203,11 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er }() Logf(2, "reproducing crash '%v': minimizing guilty program", ctx.crashDesc) - res.Prog, _ = prog.Minimize(res.Prog, -1, func(p1 *prog.Prog, callIndex int) bool { + call := -1 + if res.Opts.Fault { + call = res.Opts.FaultCall + } + res.Prog, res.Opts.FaultCall = prog.Minimize(res.Prog, call, func(p1 *prog.Prog, callIndex int) bool { crashed, err := ctx.testProg(p1, duration, res.Opts) if err != nil { Logf(1, "reproducing crash '%v': minimization failed with %v", ctx.crashDesc, err) @@ -297,12 +307,15 @@ func (ctx *context) testProg(p *prog.Prog, duration time.Duration, opts csource. return false, fmt.Errorf("failed to copy to VM: %v", err) } - repeat := "1" + repeat := 1 if opts.Repeat { - repeat = "0" + repeat = 0 + } + if !opts.Fault { + opts.FaultCall = -1 } - command := fmt.Sprintf("%v -executor %v -cover=0 -procs=%v -repeat=%v -sandbox %v -threaded=%v -collide=%v %v", - inst.execprogBin, inst.executorBin, opts.Procs, repeat, opts.Sandbox, opts.Threaded, opts.Collide, vmProgFile) + command := fmt.Sprintf("%v -executor %v -cover=0 -procs=%v -repeat=%v -sandbox %v -threaded=%v -collide=%v -fault_call=%v -fault_nth=%v %v", + inst.execprogBin, inst.executorBin, opts.Procs, repeat, opts.Sandbox, opts.Threaded, opts.Collide, opts.FaultCall, opts.FaultNth, vmProgFile) Logf(2, "reproducing crash '%v': testing program (duration=%v, %+v): %s", ctx.crashDesc, duration, opts, p) return ctx.testImpl(inst, command, duration) diff --git a/rpctype/rpctype.go b/rpctype/rpctype.go index 3adef1962..cc98f09d5 100644 --- a/rpctype/rpctype.go +++ b/rpctype/rpctype.go @@ -34,6 +34,8 @@ type ConnectRes struct { type CheckArgs struct { Name string Kcov bool + Leak bool + Fault bool Calls []string } diff --git a/symbolizer/nm_test.go b/symbolizer/nm_test.go index 109c1d5bb..5a5723838 100644 --- a/symbolizer/nm_test.go +++ b/symbolizer/nm_test.go @@ -4,27 +4,45 @@ package symbolizer import ( - "os" "testing" ) func TestSymbols(t *testing.T) { - symbols, err := ReadSymbols(os.Args[0]) + symbols, err := ReadSymbols("testdata/nm.test.out") if err != nil { t.Fatalf("failed to read symbols: %v", err) } - t.Logf("Read %v symbols", len(symbols)) - s, ok := symbols["github.com/google/syzkaller/symbolizer.TestSymbols"] - if !ok { - t.Fatalf("symbols don't contain this function") + if len(symbols) != 5 { + t.Fatalf("got %v symbols, want 5", len(symbols)) } - if len(s) != 1 { - t.Fatalf("more than 1 symbol: %v", len(s)) + { + s := symbols["barfoo"] + if len(s) != 1 { + t.Fatalf("got %v barfoo symbols, want 1", len(s)) + } + if s[0].Addr != 0x400507 { + t.Fatalf("bad barfoo address: 0x%x", s[0].Addr) + } + if s[0].Size != 0x30 { + t.Fatalf("bad barfoo size: 0x%x", s[0].Size) + } } - if s[0].Addr == 0 { - t.Fatalf("symbol address is 0") - } - if s[0].Size <= 10 || s[0].Size > 1<<20 { - t.Fatalf("bogus symbol size: %v", s[0].Size) + { + s := symbols["foobar"] + if len(s) != 2 { + t.Fatalf("got %v foobar symbols, want 2", len(s)) + } + if s[0].Addr != 0x4004ed { + t.Fatalf("bad foobar[0] address: 0x%x", s[0].Addr) + } + if s[0].Size != 0x10 { + t.Fatalf("bad foobar[0] size: 0x%x", s[0].Size) + } + if s[1].Addr != 0x4004fa { + t.Fatalf("bad foobar[1] address: 0x%x", s[1].Addr) + } + if s[1].Size != 0x10 { + t.Fatalf("bad foobar[1] size: 0x%x", s[1].Size) + } } } diff --git a/symbolizer/testdata/nm.test.out b/symbolizer/testdata/nm.test.out Binary files differnew file mode 100755 index 000000000..c6ee07240 --- /dev/null +++ b/symbolizer/testdata/nm.test.out diff --git a/sys/align.go b/sys/align.go index e1ce27d56..00d464bcb 100644 --- a/sys/align.go +++ b/sys/align.go @@ -32,7 +32,7 @@ func initAlign() { } } - for _, s := range Structs { + for _, s := range keyedStructs { rec(s) } } diff --git a/sys/decl.go b/sys/decl.go index 270ae4582..28c18d488 100644 --- a/sys/decl.go +++ b/sys/decl.go @@ -430,7 +430,86 @@ func (t *UnionType) Align() uintptr { return align } -var ctors = make(map[string][]*Call) +var ( + CallMap = make(map[string]*Call) + structs map[string]Type + keyedStructs map[structKey]Type + Resources map[string]*ResourceDesc + ctors = make(map[string][]*Call) +) + +type structKey struct { + name string + field string + dir Dir +} + +func getStruct(key structKey) Type { + if structs == nil { + structs = make(map[string]Type) + keyedStructs = make(map[structKey]Type) + for _, str := range structArray { + structs[str.Name()] = str + } + } + str := keyedStructs[key] + if str == nil { + proto := structs[key.name] + if proto == nil { + panic(fmt.Sprintf("missing struct prototype for %v", key.name)) + } + switch typed := proto.(type) { + case *StructType: + newStr := new(StructType) + *newStr = *typed + newStr.FldName = key.field + newStr.ArgDir = key.dir + str = newStr + case *UnionType: + newStr := new(UnionType) + *newStr = *typed + newStr.FldName = key.field + newStr.ArgDir = key.dir + str = newStr + default: + panic(fmt.Sprintf("unexpected type of struct prototype for %v: %+v", key.name, proto)) + } + keyedStructs[key] = str + } + return str +} + +func initStructFields() { + missed := 0 + for _, f := range structFields { + untyped := keyedStructs[f.key] + if untyped == nil { + missed++ + continue + } + switch str := untyped.(type) { + case *StructType: + str.Fields = f.fields + case *UnionType: + str.Options = f.fields + default: + panic(fmt.Sprintf("unexpected type of struct prototype for %v: %+v", f.key.name, untyped)) + } + } + fmt.Printf("missed %v/%v\n", missed, len(structFields)) +} + +func resource(name string) *ResourceDesc { + if Resources == nil { + // This is first called during init of sys package, so does not need to be thread-safe. + // resourceArray is in sys_GOARCH.go (generated by sysgen). + Resources = make(map[string]*ResourceDesc) + for _, res := range resourceArray { + Resources[res.Name] = res + } + } + return Resources[name] +} // ResourceConstructors returns a list of calls that can create a resource of the given kind. func ResourceConstructors(name string) []*Call { @@ -438,8 +517,9 @@ func ResourceConstructors(name string) []*Call { } func initResources() { - for name, res := range Resources { - ctors[name] = resourceCtors(res.Kind, false) + resource("") // init resources, if it's not done yet + for _, res := range resourceArray { + ctors[res.Name] = resourceCtors(res.Kind, false) } } @@ -521,14 +601,26 @@ func TransitivelyEnabledCalls(enabled map[*Call]bool) map[*Call]bool { for c := range enabled { supported[c] = true } + inputResources := make(map[*Call][]*ResourceType) + ctors := make(map[string][]*Call) + for c := range supported { + inputs := c.InputResources() + inputResources[c] = inputs + for _, res := range inputs { + if _, ok := ctors[res.Desc.Name]; ok { + continue + } + ctors[res.Desc.Name] = resourceCtors(res.Desc.Kind, true) + } + } for { n := len(supported) haveGettime := supported[CallMap["clock_gettime"]] for c := range supported { canCreate := true - for _, res := range c.InputResources() { + for _, res := range inputResources[c] { noctors := true - for _, ctor := range resourceCtors(res.Desc.Kind, true) { + for _, ctor := range ctors[res.Desc.Name] { if supported[ctor] { noctors = false break @@ -599,16 +691,12 @@ func ForeachType(meta *Call, f func(Type)) { } } -var ( - Calls []*Call - CallMap = make(map[string]*Call) -) - func init() { - initCalls() initStructFields() initResources() initAlign() + keyedStructs = nil + structs = nil for i, c := range Calls { c.ID = i diff --git a/sys/decl_test.go b/sys/decl_test.go index c41a95e43..18385c321 100644 --- a/sys/decl_test.go +++ b/sys/decl_test.go @@ -8,6 +8,7 @@ import ( ) func TestTransitivelyEnabledCalls(t *testing.T) { + t.Parallel() calls := make(map[*Call]bool) for _, c := range Calls { calls[c] = true @@ -37,6 +38,7 @@ func TestTransitivelyEnabledCalls(t *testing.T) { } func TestClockGettime(t *testing.T) { + t.Parallel() calls := make(map[*Call]bool) for _, c := range Calls { calls[c] = true diff --git a/sysgen/sysgen.go b/sysgen/sysgen.go index 12bcf26f7..7454ae60c 100644 --- a/sysgen/sysgen.go +++ b/sysgen/sysgen.go @@ -8,11 +8,13 @@ import ( "bytes" "flag" "fmt" - "go/format" "io" + "io/ioutil" "os" "path/filepath" "regexp" + "runtime" + "runtime/pprof" "sort" "strconv" "strings" @@ -21,7 +23,10 @@ import ( ) var ( - flagV = flag.Int("v", 0, "verbosity") + flagV = flag.Int("v", 0, "verbosity") + flagMemProfile = flag.String("memprofile", "", "write a memory profile to the file") + + intRegExp = regexp.MustCompile("^int([0-9]+|ptr)(be)?(:[0-9]+)?$") ) const ( @@ -86,6 +91,18 @@ func main() { } generateExecutorSyscalls(desc.Syscalls, consts) + + if *flagMemProfile != "" { + f, err := os.Create(*flagMemProfile) + if err != nil { + failf("could not create memory profile: ", err) + } + runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + failf("could not write memory profile: ", err) + } + f.Close() + } } func readConsts(arch string) map[string]uint64 { @@ -147,7 +164,7 @@ func generate(arch string, desc *Description, consts map[string]uint64, out io.W generateResources(desc, consts, out) generateStructs(desc, consts, out) - fmt.Fprintf(out, "func initCalls() {\n") + fmt.Fprintf(out, "var Calls = []*Call{\n") for _, s := range desc.Syscalls { logf(4, " generate population code for %v", s.Name) skipCurrentSyscall = "" @@ -160,7 +177,7 @@ func generate(arch string, desc *Description, consts map[string]uint64, out io.W logf(0, "unsupported syscall: %v", s.CallName) } } - fmt.Fprintf(out, "func() { Calls = append(Calls, &Call{Name: \"%v\", CallName: \"%v\"", s.Name, s.CallName) + fmt.Fprintf(out, "&Call{Name: \"%v\", CallName: \"%v\"", s.Name, s.CallName) if len(s.Ret) != 0 { fmt.Fprintf(out, ", Ret: ") generateArg("", "ret", s.Ret[0], "out", s.Ret[1:], desc, consts, true, false, out) @@ -177,7 +194,7 @@ func generate(arch string, desc *Description, consts map[string]uint64, out io.W logf(0, "unsupported syscall: %v due to %v", s.Name, skipCurrentSyscall) syscallNR = -1 } - fmt.Fprintf(out, "}, NR: %v})}()\n", syscallNR) + fmt.Fprintf(out, "}, NR: %v},\n", syscallNR) } fmt.Fprintf(out, "}\n\n") @@ -201,7 +218,7 @@ func generateResources(desc *Description, consts map[string]uint64, out io.Write } sort.Sort(resArray) - fmt.Fprintf(out, "var Resources = map[string]*ResourceDesc{\n") + fmt.Fprintf(out, "var resourceArray = []*ResourceDesc{\n") for _, res := range resArray { underlying := "" name := res.Name @@ -230,7 +247,7 @@ func generateResources(desc *Description, consts map[string]uint64, out io.Write res = desc.Resources[res.Base] } } - fmt.Fprintf(out, "\"%v\": &ResourceDesc{Name: \"%v\", Type: ", name, name) + fmt.Fprintf(out, "&ResourceDesc{Name: \"%v\", Type: ", name) generateArg("", "resource-type", underlying, "inout", nil, desc, consts, true, true, out) fmt.Fprintf(out, ", Kind: []string{") for i, k := range kind { @@ -260,7 +277,7 @@ type structKey struct { dir string } -func generateStructEntry(str Struct, key structKey, out io.Writer) { +func generateStructEntry(str *Struct, out io.Writer) { typ := "StructType" if str.IsUnion { typ = "UnionType" @@ -277,38 +294,31 @@ func generateStructEntry(str Struct, key structKey, out io.Writer) { if str.Align != 0 { align = fmt.Sprintf(", align: %v", str.Align) } - fmt.Fprintf(out, "\"%v\": &%v{TypeCommon: TypeCommon{TypeName: \"%v\", FldName: \"%v\", ArgDir: %v, IsOptional: %v} %v %v %v},\n", - key, typ, key.name, key.field, fmtDir(key.dir), false, packed, align, varlen) + fmt.Fprintf(out, "&%v{TypeCommon: TypeCommon{TypeName: \"%v\", IsOptional: %v} %v %v %v},\n", + typ, str.Name, false, packed, align, varlen) } -func generateStructFields(str Struct, key structKey, desc *Description, consts map[string]uint64, out io.Writer) { - typ := "StructType" - fields := "Fields" - if str.IsUnion { - typ = "UnionType" - fields = "Options" - } - fmt.Fprintf(out, "func() { s := Structs[\"%v\"].(*%v)\n", key, typ) +func generateStructFields(str *Struct, key structKey, desc *Description, consts map[string]uint64, out io.Writer) { + fmt.Fprintf(out, "{structKey{\"%v\", \"%v\", %v}, []Type{\n", key.name, key.field, fmtDir(key.dir)) for _, a := range str.Flds { - fmt.Fprintf(out, "s.%v = append(s.%v, ", fields, fields) generateArg(str.Name, a[0], a[1], key.dir, a[2:], desc, consts, false, true, out) - fmt.Fprintf(out, ")\n") + fmt.Fprintf(out, ",\n") } - fmt.Fprintf(out, "}()\n") + fmt.Fprintf(out, "}},\n") + } func generateStructs(desc *Description, consts map[string]uint64, out io.Writer) { // Struct fields can refer to other structs. Go compiler won't like if - // we refer to Structs map during Structs map initialization. So we do - // it in 2 passes: on the first pass create types and assign them to - // the map, on the second pass fill in fields. + // we refer to Structs during Structs initialization. So we do + // it in 2 passes: on the first pass create struct types without fields, + // on the second pass we fill in fields. // Since structs of the same type can be fields with different names // of multiple other structs, we have an instance of those structs - // for each field indexed by the name of the parent struct and the - // field name. + // for each field indexed by the name of the parent struct, field name and dir. - structMap := make(map[structKey]Struct) + structMap := make(map[structKey]*Struct) for _, str := range desc.Structs { for _, dir := range []string{"in", "out", "inout"} { structMap[structKey{str.Name, "", dir}] = str @@ -322,15 +332,25 @@ func generateStructs(desc *Description, consts map[string]uint64, out io.Writer) } } - fmt.Fprintf(out, "var Structs = map[string]Type{\n") - for key, str := range structMap { - generateStructEntry(str, key, out) + fmt.Fprintf(out, "var structArray = []Type{\n") + sortedStructs := make([]*Struct, 0, len(desc.Structs)) + for _, str := range desc.Structs { + sortedStructs = append(sortedStructs, str) + } + sort.Sort(structSorter(sortedStructs)) + for _, str := range sortedStructs { + generateStructEntry(str, out) } fmt.Fprintf(out, "}\n") - fmt.Fprintf(out, "func initStructFields() {\n") - for key, str := range structMap { - generateStructFields(str, key, desc, consts, out) + fmt.Fprintf(out, "var structFields = []struct{key structKey; fields []Type}{") + sortedKeys := make([]structKey, 0, len(structMap)) + for key := range structMap { + sortedKeys = append(sortedKeys, key) + } + sort.Sort(structKeySorter(sortedKeys)) + for _, key := range sortedKeys { + generateStructFields(structMap[key], key, desc, consts, out) } fmt.Fprintf(out, "}\n") } @@ -668,7 +688,6 @@ func generateArg( dir = "in" fmt.Fprintf(out, "&PtrType{%v, Type: %v}", common(), generateType(a[1], a[0], desc, consts)) default: - intRegExp := regexp.MustCompile("^int([0-9]+|ptr)(be)?(:[0-9]+)?$") if intRegExp.MatchString(typ) { canBeArg = true size, bigEndian, bitfieldLen := decodeIntType(typ) @@ -692,12 +711,12 @@ func generateArg( if len(a) != 0 { failf("struct '%v' has args", typ) } - fmt.Fprintf(out, "Structs[\"%v\"]", structKey{typ, origName, dir}) + fmt.Fprintf(out, "getStruct(structKey{\"%v\", \"%v\", %v})", typ, origName, fmtDir(dir)) } else if _, ok := desc.Resources[typ]; ok { if len(a) != 0 { failf("resource '%v' has args", typ) } - fmt.Fprintf(out, "&ResourceType{%v, Desc: Resources[\"%v\"]}", common(), typ) + fmt.Fprintf(out, "&ResourceType{%v, Desc: resource(\"%v\")}", common(), typ) return } else { failf("unknown arg type \"%v\" for %v", typ, name) @@ -787,11 +806,9 @@ func isIdentifier(s string) bool { return true } -func writeSource(file string, data []byte) { - src, err := format.Source(data) - if err != nil { - fmt.Printf("%s\n", data) - failf("failed to format output: %v", err) +func writeSource(file string, src []byte) { + if oldSrc, err := ioutil.ReadFile(file); err == nil && bytes.Equal(src, oldSrc) { + return } writeFile(file, src) } @@ -822,6 +839,32 @@ func (a ResourceArray) Len() int { return len(a) } func (a ResourceArray) Less(i, j int) bool { return a[i].Name < a[j].Name } func (a ResourceArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +type structSorter []*Struct + +func (a structSorter) Len() int { return len(a) } +func (a structSorter) Less(i, j int) bool { return a[i].Name < a[j].Name } +func (a structSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type structKeySorter []structKey + +func (a structKeySorter) Len() int { return len(a) } +func (a structKeySorter) Less(i, j int) bool { + if a[i].name < a[j].name { + return true + } + if a[i].name > a[j].name { + return false + } + if a[i].field < a[j].field { + return true + } + if a[i].field > a[j].field { + return false + } + return a[i].dir < a[j].dir +} +func (a structKeySorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + func failf(msg string, args ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", args...) os.Exit(1) diff --git a/sysparser/lexer.go b/sysparser/lexer.go index 80e6bbbe2..505b52059 100644 --- a/sysparser/lexer.go +++ b/sysparser/lexer.go @@ -16,7 +16,7 @@ type Description struct { Includes []string Defines map[string]string Syscalls []Syscall - Structs map[string]Struct + Structs map[string]*Struct Unnamed map[string][]string Flags map[string][]string StrFlags map[string][]string @@ -50,7 +50,7 @@ func Parse(in io.Reader) *Description { var includes []string defines := make(map[string]string) var syscalls []Syscall - structs := make(map[string]Struct) + structs := make(map[string]*Struct) unnamed := make(map[string][]string) flags := make(map[string][]string) strflags := make(map[string][]string) @@ -107,7 +107,7 @@ func Parse(in io.Reader) *Description { } fields[f[0]] = true } - structs[str.Name] = *str + structs[str.Name] = str str = nil } else { p.SkipWs() diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index 68046fee0..f06803d5d 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -85,8 +85,9 @@ var ( statExecSmash uint64 statNewInput uint64 - allTriaged uint32 - noCover bool + allTriaged uint32 + noCover bool + faultInjectionEnabled bool ) func main() { @@ -153,12 +154,23 @@ func main() { } } + // This requires "fault-inject: support systematic fault injection" kernel commit. + if fd, err := syscall.Open("/proc/self/fail-nth", syscall.O_RDWR, 0); err == nil { + syscall.Close(fd) + faultInjectionEnabled = true + } + if r.NeedCheck { a := &CheckArgs{Name: *flagName} if fd, err := syscall.Open("/sys/kernel/debug/kcov", syscall.O_RDWR, 0); err == nil { syscall.Close(fd) a.Kcov = true } + if fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0); err == nil { + syscall.Close(fd) + a.Leak = true + } + a.Fault = faultInjectionEnabled for c := range calls { a.Calls = append(a.Calls, c.Name) } @@ -189,6 +201,9 @@ func main() { if _, ok := calls[sys.CallMap["syz_extract_tcp_res"]]; ok { config.Flags |= ipc.FlagEnableTun } + if faultInjectionEnabled { + config.Flags |= ipc.FlagEnableFault + } noCover = config.Flags&ipc.FlagSignal == 0 leakCallback := func() { if atomic.LoadUint32(&allTriaged) != 0 { @@ -450,6 +465,9 @@ func addInput(inp RpcInput) { } func smashInput(pid int, env *ipc.Env, ct *prog.ChoiceTable, rs rand.Source, inp Input) { + if faultInjectionEnabled { + failCall(pid, env, inp.p, inp.call) + } for i := 0; i < 100; i++ { p := inp.p.Clone() p.Mutate(rs, programLength, ct, corpus) @@ -458,6 +476,21 @@ func smashInput(pid int, env *ipc.Env, ct *prog.ChoiceTable, rs rand.Source, inp } } +func failCall(pid int, env *ipc.Env, p *prog.Prog, call int) { + for nth := 0; nth < 100; nth++ { + Logf(1, "%v: injecting fault into call %v/%v in program: %v", pid, call, nth, p.String()) + opts := &ipc.ExecOpts{ + Flags: ipc.FlagInjectFault, + FaultCall: call, + FaultNth: nth, + } + info := execute1(pid, env, opts, p, &statExecSmash) + if info != nil && len(info) > call && !info[call].FaultInjected { + break + } + } +} + func triageInput(pid int, env *ipc.Env, inp Input) { if noCover { panic("should not be called when coverage is disabled") @@ -477,10 +510,13 @@ func triageInput(pid int, env *ipc.Env, inp Input) { Logf(3, "triaging input for %v (new signal=%v):\n%s", call.CallName, len(newSignal), data) var inputCover cover.Cover + opts := &ipc.ExecOpts{ + Flags: ipc.FlagCollectCover, + } if inp.minimized { // We just need to get input coverage. for i := 0; i < 3; i++ { - info := execute1(pid, env, inp.p, &statExecTriage, true) + info := execute1(pid, env, opts, inp.p, &statExecTriage) if len(info) == 0 || len(info[inp.call].Cover) == 0 { continue // The call was not executed. Happens sometimes. } @@ -491,7 +527,7 @@ func triageInput(pid int, env *ipc.Env, inp Input) { // We need to compute input coverage and non-flaky signal for minimization. notexecuted := false for i := 0; i < 3; i++ { - info := execute1(pid, env, inp.p, &statExecTriage, true) + info := execute1(pid, env, opts, inp.p, &statExecTriage) if len(info) == 0 || len(info[inp.call].Signal) == 0 { // The call was not executed. Happens sometimes. if notexecuted { @@ -563,7 +599,11 @@ func triageInput(pid int, env *ipc.Env, inp Input) { } func execute(pid int, env *ipc.Env, p *prog.Prog, needCover, minimized, candidate bool, stat *uint64) []ipc.CallInfo { - info := execute1(pid, env, p, stat, needCover) + opts := &ipc.ExecOpts{} + if needCover { + opts.Flags |= ipc.FlagCollectCover + } + info := execute1(pid, env, opts, p, stat) signalMu.RLock() defer signalMu.RUnlock() @@ -599,7 +639,7 @@ func execute(pid int, env *ipc.Env, p *prog.Prog, needCover, minimized, candidat var logMu sync.Mutex -func execute1(pid int, env *ipc.Env, p *prog.Prog, stat *uint64, needCover bool) []ipc.CallInfo { +func execute1(pid int, env *ipc.Env, opts *ipc.ExecOpts, p *prog.Prog, stat *uint64) []ipc.CallInfo { if false { // For debugging, this function must not be executed with locks held. corpusMu.Lock() @@ -610,10 +650,17 @@ func execute1(pid int, env *ipc.Env, p *prog.Prog, stat *uint64, needCover bool) triageMu.Unlock() } + opts.Flags |= ipc.FlagDedupCover + // Limit concurrency window and do leak checking once in a while. idx := gate.Enter() defer gate.Leave(idx) + strOpts := "" + if opts.Flags&ipc.FlagInjectFault != 0 { + strOpts = fmt.Sprintf(" (fault-call:%v fault-nth:%v)", opts.FaultCall, opts.FaultNth) + } + // The following output helps to understand what program crashed kernel. // It must not be intermixed. switch *flagOutput { @@ -622,19 +669,22 @@ func execute1(pid int, env *ipc.Env, p *prog.Prog, stat *uint64, needCover bool) case "stdout": data := p.Serialize() logMu.Lock() - Logf(0, "executing program %v:\n%s", pid, data) + Logf(0, "executing program %v%v:\n%s", pid, strOpts, data) logMu.Unlock() case "dmesg": fd, err := syscall.Open("/dev/kmsg", syscall.O_WRONLY, 0) if err == nil { buf := new(bytes.Buffer) - fmt.Fprintf(buf, "syzkaller: executing program %v:\n%s", pid, p.Serialize()) + fmt.Fprintf(buf, "syzkaller: executing program %v%v:\n%s", pid, strOpts, p.Serialize()) syscall.Write(fd, buf.Bytes()) syscall.Close(fd) } case "file": f, err := os.Create(fmt.Sprintf("%v-%v.prog", *flagName, pid)) if err == nil { + if strOpts != "" { + fmt.Fprintf(f, "#%v\n", strOpts) + } f.Write(p.Serialize()) f.Close() } @@ -643,7 +693,7 @@ func execute1(pid int, env *ipc.Env, p *prog.Prog, stat *uint64, needCover bool) try := 0 retry: atomic.AddUint64(stat, 1) - output, info, failed, hanged, err := env.Exec(p, needCover, true) + output, info, failed, hanged, err := env.Exec(opts, p) if failed { // BUG in output should be recognized by manager. Logf(0, "BUG: executor-detected bug:\n%s", output) diff --git a/syz-manager/manager.go b/syz-manager/manager.go index ca3552d9f..f0886326b 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -737,7 +737,8 @@ func (mgr *Manager) Check(a *CheckArgs, r *int) error { if mgr.vmChecked { return nil } - Logf(1, "fuzzer %v vm check: %v calls enabled", a.Name, len(a.Calls)) + Logf(1, "fuzzer %v vm check: %v calls enabled, kcov=%v, kleakcheck=%v, faultinjection=%v", + a.Name, len(a.Calls), a.Kcov, a.Leak, a.Fault) if len(a.Calls) == 0 { Fatalf("no system calls enabled") } diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go index 196c6bbb0..373bbf256 100644 --- a/tools/syz-execprog/execprog.go +++ b/tools/syz-execprog/execprog.go @@ -30,6 +30,8 @@ var ( flagRepeat = flag.Int("repeat", 1, "repeat execution that many times (0 for infinite loop)") flagProcs = flag.Int("procs", 1, "number of parallel processes to execute programs") flagOutput = flag.String("output", "none", "write programs to none/stdout") + flagFaultCall = flag.Int("fault_call", -1, "inject fault into this call (0-based)") + flagFaultNth = flag.Int("fault_nth", 0, "inject fault on n-th operation (0-based)") ) func main() { @@ -56,16 +58,26 @@ func main() { return } + execOpts := &ipc.ExecOpts{} config, err := ipc.DefaultConfig() if err != nil { Fatalf("%v", err) } - needCover := config.Flags&ipc.FlagSignal != 0 - dedupCover := true + if config.Flags&ipc.FlagSignal != 0 { + execOpts.Flags |= ipc.FlagCollectCover + } + execOpts.Flags |= ipc.FlagDedupCover if *flagCoverFile != "" { config.Flags |= ipc.FlagSignal - needCover = true - dedupCover = false + execOpts.Flags |= ipc.FlagCollectCover + execOpts.Flags &^= ipc.FlagDedupCover + } + + if *flagFaultCall >= 0 { + config.Flags |= ipc.FlagEnableFault + execOpts.Flags |= ipc.FlagInjectFault + execOpts.FaultCall = *flagFaultCall + execOpts.FaultNth = *flagFaultNth } handled := make(map[string]bool) @@ -119,7 +131,7 @@ func main() { Logf(0, "executing program %v:\n%s", pid, data) logMu.Unlock() } - output, info, failed, hanged, err := env.Exec(p, needCover, dedupCover) + output, info, failed, hanged, err := env.Exec(execOpts, p) if atomic.LoadUint32(&shutdown) != 0 { return false } diff --git a/tools/syz-prog2c/prog2c.go b/tools/syz-prog2c/prog2c.go index f4cc4e3e6..5cdec11f2 100644 --- a/tools/syz-prog2c/prog2c.go +++ b/tools/syz-prog2c/prog2c.go @@ -14,12 +14,14 @@ import ( ) var ( - flagThreaded = flag.Bool("threaded", false, "create threaded program") - flagCollide = flag.Bool("collide", false, "create collide program") - flagRepeat = flag.Bool("repeat", false, "repeat program infinitely or not") - flagProcs = flag.Int("procs", 4, "number of parallel processes") - flagSandbox = flag.String("sandbox", "none", "sandbox to use (none, setuid, namespace)") - flagProg = flag.String("prog", "", "file with program to convert (required)") + flagThreaded = flag.Bool("threaded", false, "create threaded program") + flagCollide = flag.Bool("collide", false, "create collide program") + flagRepeat = flag.Bool("repeat", false, "repeat program infinitely or not") + flagProcs = flag.Int("procs", 4, "number of parallel processes") + flagSandbox = flag.String("sandbox", "none", "sandbox to use (none, setuid, namespace)") + flagProg = flag.String("prog", "", "file with program to convert (required)") + flagFaultCall = flag.Int("fault_call", -1, "inject fault into this call (0-based)") + flagFaultNth = flag.Int("fault_nth", 0, "inject fault on n-th operation (0-based)") ) func main() { @@ -39,12 +41,15 @@ func main() { os.Exit(1) } opts := csource.Options{ - Threaded: *flagThreaded, - Collide: *flagCollide, - Repeat: *flagRepeat, - Procs: *flagProcs, - Sandbox: *flagSandbox, - Repro: false, + Threaded: *flagThreaded, + Collide: *flagCollide, + Repeat: *flagRepeat, + Procs: *flagProcs, + Sandbox: *flagSandbox, + Fault: *flagFaultCall >= 0, + FaultCall: *flagFaultCall, + FaultNth: *flagFaultNth, + Repro: false, } src, err := csource.Write(p, opts) if err != nil { diff --git a/tools/syz-stress/stress.go b/tools/syz-stress/stress.go index 87f016504..5885e233d 100644 --- a/tools/syz-stress/stress.go +++ b/tools/syz-stress/stress.go @@ -100,8 +100,8 @@ func execute(pid int, env *ipc.Env, p *prog.Prog) { fmt.Printf("executing program %v\n%s\n", pid, p.Serialize()) outMu.Unlock() } - - output, _, failed, hanged, err := env.Exec(p, false, false) + opts := &ipc.ExecOpts{} + output, _, failed, hanged, err := env.Exec(opts, p) if err != nil { fmt.Printf("failed to execute executor: %v\n", err) } diff --git a/vm/merger.go b/vm/merger.go index c3b5c16d0..2902eda6c 100644 --- a/vm/merger.go +++ b/vm/merger.go @@ -13,6 +13,7 @@ import ( type OutputMerger struct { Output chan []byte Err chan error + teeMu sync.Mutex tee io.Writer wg sync.WaitGroup } @@ -42,7 +43,9 @@ func (merger *OutputMerger) Add(name string, r io.ReadCloser) { if pos := bytes.LastIndexByte(pending, '\n'); pos != -1 { out := pending[:pos+1] if merger.tee != nil { + merger.teeMu.Lock() merger.tee.Write(out) + merger.teeMu.Unlock() } select { case merger.Output <- append([]byte{}, out...): @@ -56,7 +59,9 @@ func (merger *OutputMerger) Add(name string, r io.ReadCloser) { if len(pending) != 0 { pending = append(pending, '\n') if merger.tee != nil { + merger.teeMu.Lock() merger.tee.Write(pending) + merger.teeMu.Unlock() } select { case merger.Output <- pending: |
