aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-05-29 14:33:50 +0200
committerGitHub <noreply@github.com>2017-05-29 14:33:50 +0200
commitbaf825803c72cdc02bed92a5f84ec43482aa35d3 (patch)
treec9594f9450bb91ce03a5f671f53755c193077ef3
parent145e067777cb0d21644412548e67dcb934f1da5e (diff)
parenteaf1f711fc42235d0b9a73c6877d14b1b5244194 (diff)
Merge pull request #196 from dvyukov/executor-fault-inject3
fault injection and faster tests
-rw-r--r--Makefile26
-rw-r--r--csource/common.go33
-rw-r--r--csource/csource.go27
-rw-r--r--csource/csource_test.go24
-rw-r--r--executor/common.h33
-rw-r--r--executor/executor.cc48
-rw-r--r--host/host_test.go2
-rw-r--r--ipc/ipc.go75
-rw-r--r--ipc/ipc_test.go12
-rw-r--r--prog/parse.go40
-rw-r--r--prog/parse_test.go34
-rw-r--r--prog/rand.go4
-rw-r--r--report/report.go24
-rw-r--r--report/report_test.go12
-rw-r--r--repro/repro.go23
-rw-r--r--rpctype/rpctype.go2
-rw-r--r--symbolizer/nm_test.go44
-rwxr-xr-xsymbolizer/testdata/nm.test.outbin0 -> 8578 bytes
-rw-r--r--sys/align.go2
-rw-r--r--sys/decl.go110
-rw-r--r--sys/decl_test.go2
-rw-r--r--sysgen/sysgen.go125
-rw-r--r--sysparser/lexer.go6
-rw-r--r--syz-fuzzer/fuzzer.go68
-rw-r--r--syz-manager/manager.go3
-rw-r--r--tools/syz-execprog/execprog.go22
-rw-r--r--tools/syz-prog2c/prog2c.go29
-rw-r--r--tools/syz-stress/stress.go4
-rw-r--r--vm/merger.go5
29 files changed, 648 insertions, 191 deletions
diff --git a/Makefile b/Makefile
index c1ff2c2b6..68dff9660 100644
--- a/Makefile
+++ b/Makefile
@@ -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
new file mode 100755
index 000000000..c6ee07240
--- /dev/null
+++ b/symbolizer/testdata/nm.test.out
Binary files differ
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: