diff options
| -rw-r--r-- | executor/common_fuchsia.h | 2 | ||||
| -rw-r--r-- | executor/executor.h | 98 | ||||
| -rw-r--r-- | executor/executor_fuchsia.cc | 1 | ||||
| -rw-r--r-- | pkg/csource/fuchsia_common.go | 2 | ||||
| -rw-r--r-- | pkg/ipc/ipc.go | 125 | ||||
| -rw-r--r-- | syz-fuzzer/testing.go | 5 | ||||
| -rw-r--r-- | tools/syz-execprog/execprog.go | 6 |
7 files changed, 135 insertions, 104 deletions
diff --git a/executor/common_fuchsia.h b/executor/common_fuchsia.h index 7db03e05e..f7b36ee48 100644 --- a/executor/common_fuchsia.h +++ b/executor/common_fuchsia.h @@ -67,7 +67,7 @@ static void segv_handler() longjmp(segv_env, 1); } debug("recover: exiting\n"); - doexit(1); + doexit(SIGSEGV); } static void* ex_handler(void* arg) diff --git a/executor/executor.h b/executor/executor.h index 15e1aa40b..34c7efd48 100644 --- a/executor/executor.h +++ b/executor/executor.h @@ -142,6 +142,18 @@ struct execute_reply { uint32 status; }; +struct call_reply { + execute_reply header; + uint32 call_index; + uint32 call_num; + uint32 reserrno; + uint32 fault_injected; + uint32 signal_size; + uint32 cover_size; + uint32 comps_size; + // signal/cover/comps follow +}; + enum { KCOV_CMP_CONST = 1, KCOV_CMP_SIZE1 = 0, @@ -581,44 +593,60 @@ void handle_completion(thread_t* th) } } if (!collide && !th->colliding) { - write_output(th->call_index); - write_output(th->call_num); uint32 reserrno = th->res != -1 ? 0 : th->reserrno; - write_output(reserrno); - write_output(th->fault_injected); - uint32* signal_count_pos = write_output(0); // filled in later - uint32* cover_count_pos = write_output(0); // filled in later - uint32* comps_count_pos = write_output(0); // filled in later - - if (flag_collect_comps) { - // Collect only the comparisons - uint32 ncomps = th->cover_size; - kcov_comparison_t* start = (kcov_comparison_t*)(th->cover_data + sizeof(uint64)); - kcov_comparison_t* end = start + ncomps; - if ((char*)end > th->cover_end) - fail("too many comparisons %u", ncomps); - std::sort(start, end); - ncomps = std::unique(start, end) - start; - uint32 comps_size = 0; - for (uint32 i = 0; i < ncomps; ++i) { - if (start[i].ignore()) - continue; - comps_size++; - start[i].write(); + if (SYZ_EXECUTOR_USES_SHMEM) { + write_output(th->call_index); + write_output(th->call_num); + write_output(reserrno); + write_output(th->fault_injected); + uint32* signal_count_pos = write_output(0); // filled in later + uint32* cover_count_pos = write_output(0); // filled in later + uint32* comps_count_pos = write_output(0); // filled in later + + if (flag_collect_comps) { + // Collect only the comparisons + uint32 ncomps = th->cover_size; + kcov_comparison_t* start = (kcov_comparison_t*)(th->cover_data + sizeof(uint64)); + kcov_comparison_t* end = start + ncomps; + if ((char*)end > th->cover_end) + fail("too many comparisons %u", ncomps); + std::sort(start, end); + ncomps = std::unique(start, end) - start; + uint32 comps_size = 0; + for (uint32 i = 0; i < ncomps; ++i) { + if (start[i].ignore()) + continue; + comps_size++; + start[i].write(); + } + // Write out number of comparisons. + *comps_count_pos = comps_size; + } else if (flag_cover) { + if (is_kernel_64_bit) + write_coverage_signal<uint64>(th, signal_count_pos, cover_count_pos); + else + write_coverage_signal<uint32>(th, signal_count_pos, cover_count_pos); } - // Write out number of comparisons. - *comps_count_pos = comps_size; - } else if (flag_cover) { - if (is_kernel_64_bit) - write_coverage_signal<uint64>(th, signal_count_pos, cover_count_pos); - else - write_coverage_signal<uint32>(th, signal_count_pos, cover_count_pos); + debug("out #%u: index=%u num=%u errno=%d sig=%u cover=%u comps=%u\n", + completed, th->call_index, th->call_num, reserrno, + *signal_count_pos, *cover_count_pos, *comps_count_pos); + completed++; + write_completed(completed); + } else { + call_reply reply; + reply.header.magic = kOutMagic; + reply.header.done = 0; + reply.header.status = 0; + reply.call_index = th->call_index; + reply.call_num = th->call_num; + reply.reserrno = reserrno; + reply.fault_injected = th->fault_injected; + reply.signal_size = 0; + reply.cover_size = 0; + reply.comps_size = 0; + if (write(kOutPipeFd, &reply, sizeof(reply)) != sizeof(reply)) + fail("control pipe call write failed"); } - debug("out #%u: index=%u num=%u errno=%d sig=%u cover=%u comps=%u\n", - completed, th->call_index, th->call_num, reserrno, - *signal_count_pos, *cover_count_pos, *comps_count_pos); - completed++; - write_completed(completed); } th->handled = true; running--; diff --git a/executor/executor_fuchsia.cc b/executor/executor_fuchsia.cc index ab6d627a3..2377ad475 100644 --- a/executor/executor_fuchsia.cc +++ b/executor/executor_fuchsia.cc @@ -27,6 +27,7 @@ int main(int argc, char** argv) install_segv_handler(); main_init(); execute_one(); + reply_execute(0); (void)error; // prevent unused function warning return 0; } diff --git a/pkg/csource/fuchsia_common.go b/pkg/csource/fuchsia_common.go index 5fe83bb72..9d14c5e8d 100644 --- a/pkg/csource/fuchsia_common.go +++ b/pkg/csource/fuchsia_common.go @@ -248,7 +248,7 @@ static void segv_handler() longjmp(segv_env, 1); } debug("recover: exiting\n"); - doexit(1); + doexit(SIGSEGV); } static void* ex_handler(void* arg) diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go index b25573483..b488da277 100644 --- a/pkg/ipc/ipc.go +++ b/pkg/ipc/ipc.go @@ -214,6 +214,7 @@ func MakeEnv(config *Config, pid int) (*Env, error) { }() } else { inmem = make([]byte, prog.ExecBufferSize) + outmem = make([]byte, outputSize) } env := &Env{ in: inmem, @@ -290,35 +291,35 @@ func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info []CallIn if env.config.Flags&FlagUseShmem == 0 { progData = env.in[:progSize] } - if env.out != nil { - // Zero out the first two words (ncmd and nsig), so that we don't have garbage there - // if executor crashes before writing non-garbage there. - for i := 0; i < 4; i++ { - env.out[i] = 0 - } + // Zero out the first two words (ncmd and nsig), so that we don't have garbage there + // if executor crashes before writing non-garbage there. + for i := 0; i < 4; i++ { + env.out[i] = 0 } atomic.AddUint64(&env.StatExecs, 1) if env.cmd == nil { atomic.AddUint64(&env.StatRestarts, 1) - env.cmd, err0 = makeCommand(env.pid, env.bin, env.config, env.inFile, env.outFile) + env.cmd, err0 = makeCommand(env.pid, env.bin, env.config, env.inFile, env.outFile, env.out) if err0 != nil { return } } var restart bool output, failed, hanged, restart, err0 = env.cmd.exec(opts, progData) - if err0 != nil || restart { + if err0 != nil { env.cmd.close() env.cmd = nil return } - if env.out != nil { - info, err0 = env.readOutCoverage(p) - if info != nil && env.config.Flags&FlagSignal == 0 { - addFallbackSignal(p, info) - } + info, err0 = env.readOutCoverage(p) + if info != nil && env.config.Flags&FlagSignal == 0 { + addFallbackSignal(p, info) + } + if restart { + env.cmd.close() + env.cmd = nil } return } @@ -489,6 +490,7 @@ type command struct { exited chan struct{} inrp *os.File outwp *os.File + outmem []byte } const ( @@ -526,21 +528,18 @@ type executeReply struct { } // TODO(dvyukov): we currently parse this manually, should cast output to this struct instead. -/* type callReply struct { callIndex uint32 callNum uint32 errno uint32 - blocked uint32 faultInjected uint32 signalSize uint32 coverSize uint32 compsSize uint32 // signal/cover/comps follow } -*/ -func makeCommand(pid int, bin []string, config *Config, inFile *os.File, outFile *os.File) (*command, error) { +func makeCommand(pid int, bin []string, config *Config, inFile *os.File, outFile *os.File, outmem []byte) (*command, error) { dir, err := ioutil.TempDir("./", "syzkaller-testdir") if err != nil { return nil, fmt.Errorf("failed to create temp dir: %v", err) @@ -552,6 +551,7 @@ func makeCommand(pid int, bin []string, config *Config, inFile *os.File, outFile config: config, timeout: sanitizeTimeout(config), dir: dir, + outmem: outmem, } defer func() { if c != nil { @@ -784,44 +784,57 @@ func (c *command) exec(opts *ExecOpts, progData []byte) (output []byte, failed, hang <- false } }() - var exitStatus int - if c.config.Flags&FlagUseForkServer == 0 { - restart = true - c.cmd.Wait() - close(done) - <-hang - exitStatus = osutil.ProcessExitStatus(c.cmd.ProcessState) - } else { + restart = c.config.Flags&FlagUseForkServer == 0 + exitStatus := -1 + completedCalls := (*uint32)(unsafe.Pointer(&c.outmem[0])) + outmem := c.outmem[4:] + for { reply := &executeReply{} replyData := (*[unsafe.Sizeof(*reply)]byte)(unsafe.Pointer(reply))[:] - _, readErr := io.ReadFull(c.inrp, replyData) - close(done) - if readErr == nil { - if reply.magic != outMagic { - panic(fmt.Sprintf("executor %v: got bad reply magic 0x%x", c.pid, reply.magic)) - } - if reply.done == 0 { - // TODO: call completion/coverage over the control pipe is not supported yet. - panic(fmt.Sprintf("executor %v: got call reply", c.pid)) - } - if reply.status == 0 { - // Program was OK. - <-hang - return - } - // Executor writes magic values into the pipe before exiting, - // so proceed with killing and joining it. + if _, err := io.ReadFull(c.inrp, replyData); err != nil { + break } - c.abort() - output = <-c.readDone - if err := c.wait(); <-hang { - hanged = true - // In all likelihood, this will be duplicated by the default - // case below, but that's fine. - output = append(output, []byte(err.Error())...) - output = append(output, '\n') + if reply.magic != outMagic { + fmt.Fprintf(os.Stderr, "executor %v: got bad reply magic 0x%x\n", c.pid, reply.magic) + os.Exit(1) + } + if reply.done != 0 { + exitStatus = int(reply.status) + break + } + callReply := &callReply{} + callReplyData := (*[unsafe.Sizeof(*callReply)]byte)(unsafe.Pointer(callReply))[:] + if _, err := io.ReadFull(c.inrp, callReplyData); err != nil { + break + } + if callReply.signalSize != 0 || callReply.coverSize != 0 || callReply.compsSize != 0 { + // This is unsupported yet. + fmt.Fprintf(os.Stderr, "executor %v: got call reply with coverage\n", c.pid) + os.Exit(1) + } + copy(outmem, callReplyData) + outmem = outmem[len(callReplyData):] + *completedCalls++ + } + close(done) + if exitStatus == 0 { + // Program was OK. + <-hang + return + } + c.abort() + output = <-c.readDone + if err := c.wait(); <-hang { + hanged = true + // In all likelihood, this will be duplicated by the default + // case below, but that's fine. + output = append(output, []byte(err.Error())...) + output = append(output, '\n') + } else if exitStatus == -1 { + exitStatus = osutil.ProcessExitStatus(c.cmd.ProcessState) + if exitStatus == 0 { + exitStatus = statusRetry // fuchsia always returns wrong exit status 0 } - exitStatus = int(reply.status) } // Handle magic values returned by executor. switch exitStatus { @@ -839,15 +852,7 @@ func (c *command) exec(opts *ExecOpts, progData []byte) (output []byte, failed, hanged = false restart = true default: - if c.config.Flags&FlagUseForkServer == 0 { - return - } - // Failed to get a valid (or perhaps any) status from the executor. - // - // Once the executor is serving the status is always written to - // the pipe, so we don't bother to check the specific exit codes from wait. - err0 = fmt.Errorf("executor %v: invalid status %d, exit status: %s", - c.pid, exitStatus, c.cmd.ProcessState) + err0 = fmt.Errorf("executor %v: exit status %d", c.pid, exitStatus) } return } diff --git a/syz-fuzzer/testing.go b/syz-fuzzer/testing.go index 0819d4dc4..6418bf3b4 100644 --- a/syz-fuzzer/testing.go +++ b/syz-fuzzer/testing.go @@ -140,11 +140,6 @@ func checkSimpleProgram(args *checkArgs) error { if failed { return fmt.Errorf("program failed:\n%s", output) } - if args.ipcConfig.Flags&ipc.FlagUseShmem == 0 { - // Output is currently only supported via shmem. - // So if we are using pipes, we won't get any info. - return nil - } if len(info) == 0 { return fmt.Errorf("no calls executed:\n%s", output) } diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go index 8fc0a4aa1..d6b9822fe 100644 --- a/tools/syz-execprog/execprog.go +++ b/tools/syz-execprog/execprog.go @@ -131,8 +131,10 @@ func main() { failed, hanged, err, output) } if len(info) != 0 { - log.Logf(1, "RESULT: signal %v, coverage %v errno %v", - len(info[0].Signal), len(info[0].Cover), info[0].Errno) + for i, inf := range info { + log.Logf(1, "CALL %v: signal %v, coverage %v errno %v", + i, len(inf.Signal), len(inf.Cover), inf.Errno) + } } else { log.Logf(1, "RESULT: no calls executed") } |
