sys/test/test: add a hanging test

Ensure that we can handle hanging syscalls in all modes.
This commit is contained in:
Dmitry Vyukov 2020-09-12 11:05:45 +02:00
parent cc8045ff1f
commit 306464056c
7 changed files with 95 additions and 52 deletions

View File

@ -151,7 +151,7 @@ static void kill_and_wait(int pid, int* status)
#if !GOOS_windows
#if SYZ_EXECUTOR || SYZ_THREADED || SYZ_REPEAT && SYZ_EXECUTOR_USES_FORK_SERVER || \
__NR_syz_usb_connect || __NR_syz_usb_connect_ath9k
__NR_syz_usb_connect || __NR_syz_usb_connect_ath9k || __NR_syz_sleep_ms
static void sleep_ms(uint64 ms)
{
usleep(ms * 1000);

View File

@ -36,6 +36,15 @@ static long syz_exit(volatile long status)
}
#endif
#if SYZ_EXECUTOR || __NR_syz_sleep_ms
// syz_sleep_ms(ms intptr)
static long syz_sleep_ms(volatile long ms)
{
sleep_ms(ms);
return 0;
}
#endif
#if SYZ_EXECUTOR || __NR_syz_compare
#include <errno.h>
#include <string.h>

View File

@ -950,7 +950,7 @@ void copyout_call_results(thread_t* th)
void write_call_output(thread_t* th, bool finished)
{
uint32 reserrno = 999;
const bool blocked = th != last_scheduled;
const bool blocked = finished && th != last_scheduled;
uint32 call_flags = call_flag_executed | (blocked ? call_flag_blocked : 0);
if (finished) {
reserrno = th->res != -1 ? 0 : th->reserrno;

View File

@ -131,7 +131,7 @@ static void kill_and_wait(int pid, int* status)
#if !GOOS_windows
#if SYZ_EXECUTOR || SYZ_THREADED || SYZ_REPEAT && SYZ_EXECUTOR_USES_FORK_SERVER || \
__NR_syz_usb_connect || __NR_syz_usb_connect_ath9k
__NR_syz_usb_connect || __NR_syz_usb_connect_ath9k || __NR_syz_sleep_ms
static void sleep_ms(uint64 ms)
{
usleep(ms * 1000);
@ -9226,6 +9226,14 @@ static long syz_exit(volatile long status)
}
#endif
#if SYZ_EXECUTOR || __NR_syz_sleep_ms
static long syz_sleep_ms(volatile long ms)
{
sleep_ms(ms);
return 0;
}
#endif
#if SYZ_EXECUTOR || __NR_syz_compare
#include <errno.h>
#include <string.h>

View File

@ -441,9 +441,16 @@ func (ctx *Context) createCTest(p *prog.Prog, sandbox string, threaded bool, tim
if err != nil {
return nil, fmt.Errorf("failed to build C program: %v", err)
}
var ipcFlags ipc.ExecFlags
if threaded {
ipcFlags |= ipc.FlagThreaded | ipc.FlagCollide
}
req := &RunRequest{
P: p,
Bin: bin,
P: p,
Bin: bin,
Opts: &ipc.ExecOpts{
Flags: ipcFlags,
},
Repeat: times,
}
return req, nil
@ -463,58 +470,73 @@ func checkResult(req *RunRequest) error {
}
calls := make(map[string]bool)
for run, info := range req.Info {
for i, inf := range info.Calls {
want := req.results.Calls[i]
for flag, what := range map[ipc.CallFlags]string{
ipc.CallExecuted: "executed",
ipc.CallBlocked: "blocked",
ipc.CallFinished: "finished",
} {
if isC && flag == ipc.CallBlocked {
// C code does not detect when a call was blocked.
continue
}
if runtime.GOOS == "freebsd" && flag == ipc.CallBlocked {
// Blocking detection is flaky on freebsd.
// TODO(dvyukov): try to increase the timeout in executor to make it non-flaky.
continue
}
if (inf.Flags^want.Flags)&flag != 0 {
not := " not"
if inf.Flags&flag != 0 {
not = ""
}
return fmt.Errorf("run %v: call %v is%v %v", run, i, not, what)
}
for call := range info.Calls {
if err := checkCallResult(req, isC, run, call, info, calls); err != nil {
return err
}
if inf.Flags&ipc.CallFinished != 0 && inf.Errno != want.Errno {
return fmt.Errorf("run %v: wrong call %v result %v, want %v",
run, i, inf.Errno, want.Errno)
}
if isC || inf.Flags&ipc.CallExecuted == 0 {
}
}
return nil
}
func checkCallResult(req *RunRequest, isC bool, run, call int, info *ipc.ProgInfo, calls map[string]bool) error {
inf := info.Calls[call]
want := req.results.Calls[call]
for flag, what := range map[ipc.CallFlags]string{
ipc.CallExecuted: "executed",
ipc.CallBlocked: "blocked",
ipc.CallFinished: "finished",
} {
if flag != ipc.CallFinished {
if isC {
// C code does not detect blocked/non-finished calls.
continue
}
if req.Cfg.Flags&ipc.FlagSignal != 0 {
// Signal is always deduplicated, so we may not get any signal
// on a second invocation of the same syscall.
// For calls that are not meant to collect synchronous coverage we
// allow the signal to be empty as long as the extra signal is not.
callName := req.P.Calls[i].Meta.CallName
if len(inf.Signal) < 2 && !calls[callName] && len(info.Extra.Signal) == 0 {
return fmt.Errorf("run %v: call %v: no signal", run, i)
}
// syz_btf_id_by_name is a pseudo-syscall that might not provide
// any coverage when invoked.
if len(inf.Cover) == 0 && callName != "syz_btf_id_by_name" {
return fmt.Errorf("run %v: call %v: no cover", run, i)
}
calls[callName] = true
} else {
if len(inf.Signal) == 0 {
return fmt.Errorf("run %v: call %v: no fallback signal", run, i)
}
if req.Opts.Flags&ipc.FlagThreaded == 0 {
// In non-threaded mode blocked syscalls will block main thread
// and we won't detect blocked/unfinished syscalls.
continue
}
}
if runtime.GOOS == "freebsd" && flag == ipc.CallBlocked {
// Blocking detection is flaky on freebsd.
// TODO(dvyukov): try to increase the timeout in executor to make it non-flaky.
continue
}
if (inf.Flags^want.Flags)&flag != 0 {
not := " not"
if inf.Flags&flag != 0 {
not = ""
}
return fmt.Errorf("run %v: call %v is%v %v", run, call, not, what)
}
}
if inf.Flags&ipc.CallFinished != 0 && inf.Errno != want.Errno {
return fmt.Errorf("run %v: wrong call %v result %v, want %v",
run, call, inf.Errno, want.Errno)
}
if isC || inf.Flags&ipc.CallExecuted == 0 {
return nil
}
if req.Cfg.Flags&ipc.FlagSignal != 0 {
// Signal is always deduplicated, so we may not get any signal
// on a second invocation of the same syscall.
// For calls that are not meant to collect synchronous coverage we
// allow the signal to be empty as long as the extra signal is not.
callName := req.P.Calls[call].Meta.CallName
if len(inf.Signal) < 2 && !calls[callName] && len(info.Extra.Signal) == 0 {
return fmt.Errorf("run %v: call %v: no signal", run, call)
}
// syz_btf_id_by_name is a pseudo-syscall that might not provide
// any coverage when invoked.
if len(inf.Cover) == 0 && callName != "syz_btf_id_by_name" {
return fmt.Errorf("run %v: call %v: no cover", run, call)
}
calls[callName] = true
} else {
if len(inf.Signal) == 0 {
return fmt.Errorf("run %v: call %v: no fallback signal", run, call)
}
}
return nil
}

View File

@ -4,6 +4,7 @@
syz_mmap(addr vma, len len[addr])
syz_errno(v int32)
syz_exit(status int32)
syz_sleep_ms(ms intptr)
syz_compare(want ptr[in, string], want_len bytesize[want], got ptr[in, compare_data], got_len bytesize[got])
syz_compare_int$2(n const[2], v0 intptr, v1 intptr)
syz_compare_int$3(n const[3], v0 intptr, v1 intptr, v2 intptr)

3
sys/test/test/sleep Normal file
View File

@ -0,0 +1,3 @@
# requires: threaded
syz_sleep_ms(0x1000000) # unfinished