aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2024-11-19 16:42:32 +0100
committerDmitry Vyukov <dvyukov@google.com>2024-11-20 11:33:58 +0000
commitf56b4dcc82d7af38bf94d643c5750cf49a91a297 (patch)
tree19b2ea6bfcbf61ab7287d420f39c45432bd9b4cc
parent7d02db5adbb376babbbd3199f8c530e468292727 (diff)
pkg/manager: show number of times coverage for each call has overflowed
If the overflows happen often, it's bad. Add visibility into this.
-rw-r--r--executor/executor.cc5
-rw-r--r--executor/executor_linux.h12
-rw-r--r--pkg/flatrpc/flatrpc.fbs1
-rw-r--r--pkg/flatrpc/flatrpc.go27
-rw-r--r--pkg/flatrpc/flatrpc.h20
-rw-r--r--pkg/fuzzer/fuzzer.go22
-rw-r--r--pkg/fuzzer/stats.go20
-rw-r--r--pkg/manager/html/syscalls.html6
-rw-r--r--pkg/manager/http.go36
-rw-r--r--prog/prog.go4
10 files changed, 118 insertions, 35 deletions
diff --git a/executor/executor.cc b/executor/executor.cc
index 5b5e06422..603f23f73 100644
--- a/executor/executor.cc
+++ b/executor/executor.cc
@@ -362,6 +362,8 @@ struct cover_t {
// offset (VM_MIN_KERNEL_ADDRESS for AMD64) and then truncates the result to
// uint32_t. We get this from the 'offset' member in ksancov_trace.
intptr_t pc_offset;
+ // The coverage buffer has overflowed and we have truncated coverage.
+ bool overflow;
};
struct thread_t {
@@ -1177,6 +1179,7 @@ uint32 write_comparisons(flatbuffers::FlatBufferBuilder& fbb, cover_t* cov)
kcov_comparison_t* cov_start = (kcov_comparison_t*)(cov->data + sizeof(uint64));
if ((char*)(cov_start + ncomps) > cov->data_end)
failmsg("too many comparisons", "ncomps=%llu", ncomps);
+ cov->overflow = ((char*)(cov_start + ncomps + 1) > cov->data_end);
rpc::ComparisonRaw* start = (rpc::ComparisonRaw*)cov_start;
rpc::ComparisonRaw* end = start;
// We will convert kcov_comparison_t to ComparisonRaw inplace.
@@ -1295,6 +1298,8 @@ void write_output(int index, cover_t* cov, rpc::CallFlag flags, uint32 error, bo
}
rpc::CallInfoRawBuilder builder(*output_builder);
+ if (cov->overflow)
+ flags |= rpc::CallFlag::CoverageOverflow;
builder.add_flags(flags);
builder.add_error(error);
if (signal_off)
diff --git a/executor/executor_linux.h b/executor/executor_linux.h
index 82a1d559a..f2e9d4df6 100644
--- a/executor/executor_linux.h
+++ b/executor/executor_linux.h
@@ -180,14 +180,22 @@ static void cover_reset(cover_t* cov)
cover_unprotect(cov);
*(uint64*)cov->data = 0;
cover_protect(cov);
+ cov->overflow = false;
+}
+
+template <typename cover_data_t>
+static void cover_collect_impl(cover_t* cov)
+{
+ cov->size = *(cover_data_t*)cov->data;
+ cov->overflow = (cov->data + (cov->size + 2) * sizeof(cover_data_t)) > cov->data_end;
}
static void cover_collect(cover_t* cov)
{
if (is_kernel_64_bit)
- cov->size = *(uint64*)cov->data;
+ cover_collect_impl<uint64>(cov);
else
- cov->size = *(uint32*)cov->data;
+ cover_collect_impl<uint32>(cov);
}
// One does not simply exit.
diff --git a/pkg/flatrpc/flatrpc.fbs b/pkg/flatrpc/flatrpc.fbs
index 51066d9a4..0bd32b743 100644
--- a/pkg/flatrpc/flatrpc.fbs
+++ b/pkg/flatrpc/flatrpc.fbs
@@ -202,6 +202,7 @@ enum CallFlag : uint8 (bit_flags) {
Finished, // finished executing (rather than blocked forever)
Blocked, // finished but blocked during execution
FaultInjected, // fault was injected into this call
+ CoverageOverflow, // coverage buffer has overflowed so we have truncated coverage
}
table CallInfoRaw {
diff --git a/pkg/flatrpc/flatrpc.go b/pkg/flatrpc/flatrpc.go
index 0a9c0e0d0..8445cde2d 100644
--- a/pkg/flatrpc/flatrpc.go
+++ b/pkg/flatrpc/flatrpc.go
@@ -392,24 +392,27 @@ func (v ExecFlag) String() string {
type CallFlag byte
const (
- CallFlagExecuted CallFlag = 1
- CallFlagFinished CallFlag = 2
- CallFlagBlocked CallFlag = 4
- CallFlagFaultInjected CallFlag = 8
+ CallFlagExecuted CallFlag = 1
+ CallFlagFinished CallFlag = 2
+ CallFlagBlocked CallFlag = 4
+ CallFlagFaultInjected CallFlag = 8
+ CallFlagCoverageOverflow CallFlag = 16
)
var EnumNamesCallFlag = map[CallFlag]string{
- CallFlagExecuted: "Executed",
- CallFlagFinished: "Finished",
- CallFlagBlocked: "Blocked",
- CallFlagFaultInjected: "FaultInjected",
+ CallFlagExecuted: "Executed",
+ CallFlagFinished: "Finished",
+ CallFlagBlocked: "Blocked",
+ CallFlagFaultInjected: "FaultInjected",
+ CallFlagCoverageOverflow: "CoverageOverflow",
}
var EnumValuesCallFlag = map[string]CallFlag{
- "Executed": CallFlagExecuted,
- "Finished": CallFlagFinished,
- "Blocked": CallFlagBlocked,
- "FaultInjected": CallFlagFaultInjected,
+ "Executed": CallFlagExecuted,
+ "Finished": CallFlagFinished,
+ "Blocked": CallFlagBlocked,
+ "FaultInjected": CallFlagFaultInjected,
+ "CoverageOverflow": CallFlagCoverageOverflow,
}
func (v CallFlag) String() string {
diff --git a/pkg/flatrpc/flatrpc.h b/pkg/flatrpc/flatrpc.h
index 5232114b3..94e1c15f6 100644
--- a/pkg/flatrpc/flatrpc.h
+++ b/pkg/flatrpc/flatrpc.h
@@ -649,23 +649,25 @@ enum class CallFlag : uint8_t {
Finished = 2,
Blocked = 4,
FaultInjected = 8,
+ CoverageOverflow = 16,
NONE = 0,
- ANY = 15
+ ANY = 31
};
FLATBUFFERS_DEFINE_BITMASK_OPERATORS(CallFlag, uint8_t)
-inline const CallFlag (&EnumValuesCallFlag())[4] {
+inline const CallFlag (&EnumValuesCallFlag())[5] {
static const CallFlag values[] = {
CallFlag::Executed,
CallFlag::Finished,
CallFlag::Blocked,
- CallFlag::FaultInjected
+ CallFlag::FaultInjected,
+ CallFlag::CoverageOverflow
};
return values;
}
inline const char * const *EnumNamesCallFlag() {
- static const char * const names[9] = {
+ static const char * const names[17] = {
"Executed",
"Finished",
"",
@@ -674,13 +676,21 @@ inline const char * const *EnumNamesCallFlag() {
"",
"",
"FaultInjected",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "CoverageOverflow",
nullptr
};
return names;
}
inline const char *EnumNameCallFlag(CallFlag e) {
- if (flatbuffers::IsOutRange(e, CallFlag::Executed, CallFlag::FaultInjected)) return "";
+ if (flatbuffers::IsOutRange(e, CallFlag::Executed, CallFlag::CoverageOverflow)) return "";
const size_t index = static_cast<size_t>(e) - static_cast<size_t>(CallFlag::Executed);
return EnumNamesCallFlag()[index];
}
diff --git a/pkg/fuzzer/fuzzer.go b/pkg/fuzzer/fuzzer.go
index 3dac022ad..3e5f94fc3 100644
--- a/pkg/fuzzer/fuzzer.go
+++ b/pkg/fuzzer/fuzzer.go
@@ -50,7 +50,7 @@ func NewFuzzer(ctx context.Context, cfg *Config, rnd *rand.Rand,
}
}
f := &Fuzzer{
- Stats: newStats(),
+ Stats: newStats(target),
Config: cfg,
Cover: newCover(),
@@ -170,6 +170,10 @@ func (fuzzer *Fuzzer) processResult(req *queue.Request, res *queue.Result, flags
if res.Info != nil {
fuzzer.statExecTime.Add(int(res.Info.Elapsed / 1e6))
+ for call, info := range res.Info.Calls {
+ fuzzer.handleCallInfo(req, info, call)
+ }
+ fuzzer.handleCallInfo(req, res.Info.Extra, -1)
}
// Corpus candidates may have flaky coverage, so we give them a second chance.
@@ -231,6 +235,22 @@ func (fuzzer *Fuzzer) triageProgCall(p *prog.Prog, info *flatrpc.CallInfo, call
}
}
+func (fuzzer *Fuzzer) handleCallInfo(req *queue.Request, info *flatrpc.CallInfo, call int) {
+ if info == nil || info.Flags&flatrpc.CallFlagCoverageOverflow == 0 {
+ return
+ }
+ syscallIdx := len(fuzzer.Syscalls) - 1
+ if call != -1 {
+ syscallIdx = req.Prog.Calls[call].Meta.ID
+ }
+ stat := &fuzzer.Syscalls[syscallIdx]
+ if req.ExecOpts.ExecFlags&flatrpc.ExecFlagCollectComps != 0 {
+ stat.CompsOverflows.Add(1)
+ } else {
+ stat.CoverOverflows.Add(1)
+ }
+}
+
func signalPrio(p *prog.Prog, info *flatrpc.CallInfo, call int) (prio uint8) {
if call == -1 {
return 0
diff --git a/pkg/fuzzer/stats.go b/pkg/fuzzer/stats.go
index 7990f8b13..40c71d309 100644
--- a/pkg/fuzzer/stats.go
+++ b/pkg/fuzzer/stats.go
@@ -3,9 +3,17 @@
package fuzzer
-import "github.com/google/syzkaller/pkg/stat"
+import (
+ "sync/atomic"
+
+ "github.com/google/syzkaller/pkg/stat"
+ "github.com/google/syzkaller/prog"
+)
type Stats struct {
+ // Indexed by prog.Syscall.ID + the last element for extra/remote.
+ Syscalls []SyscallStats
+
statCandidates *stat.Val
statNewInputs *stat.Val
statJobs *stat.Val
@@ -27,8 +35,16 @@ type Stats struct {
statExecCollide *stat.Val
}
-func newStats() Stats {
+type SyscallStats struct {
+ // Number of times coverage buffer for this syscall has overflowed.
+ CoverOverflows atomic.Uint64
+ // Number of times comparisons buffer for this syscall has overflowed.
+ CompsOverflows atomic.Uint64
+}
+
+func newStats(target *prog.Target) Stats {
return Stats{
+ Syscalls: make([]SyscallStats, len(target.Syscalls)+1),
statCandidates: stat.New("candidates", "Number of candidate programs in triage queue",
stat.Console, stat.Graph("corpus")),
statNewInputs: stat.New("new inputs", "Potential untriaged corpus candidates",
diff --git a/pkg/manager/html/syscalls.html b/pkg/manager/html/syscalls.html
index e3edfcabf..d16337978 100644
--- a/pkg/manager/html/syscalls.html
+++ b/pkg/manager/html/syscalls.html
@@ -9,7 +9,9 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the
<th><a onclick="return sortTable(this, 'Syscall', textSort)" href="#">Syscall</a></th>
<th><a onclick="return sortTable(this, 'Inputs', numSort)" href="#" title="Number of inputs in the corpus added because of this syscall">Inputs</a></th>
<th><a onclick="return sortTable(this, 'Total', numSort)" href="#" title="Total number of inputs in the corpus that contain this syscall">Total</a></th>
- <th><a onclick="return sortTable(this, 'Coverage', numSort)" href="#">Coverage</a></th>
+ <th><a onclick="return sortTable(this, 'Coverage', numSort)" href="#" title="Coverage achieved by this syscall">Coverage</a></th>
+ <th><a onclick="return sortTable(this, 'Cover overflows', numSort)" href="#" title="Number of times coverage buffer has overflowed on this syscall">Cover overflows</a></th>
+ <th><a onclick="return sortTable(this, 'Comps overflows', numSort)" href="#" title="Number of times comparisons buffer has overflowed on this syscall">Comps overflows</a></th>
<th>Prio</th>
</tr>
{{range $c := $.Calls}}
@@ -18,6 +20,8 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the
<td><a href='/corpus?call={{$c.Name}}'>{{$c.Inputs}}</a></td>
<td>{{$c.Total}}</td>
<td><a href='/cover?call={{$c.Name}}'>{{$c.Cover}}</a></td>
+ <td>{{$c.CoverOverflows}}</td>
+ <td>{{$c.CompsOverflows}}</td>
<td><a href='/prio?call={{$c.Name}}'>prio</a></td>
</tr>
{{end}}
diff --git a/pkg/manager/http.go b/pkg/manager/http.go
index 81affcf7e..cbde6d151 100644
--- a/pkg/manager/http.go
+++ b/pkg/manager/http.go
@@ -187,6 +187,7 @@ func (serv *HTTPServer) textPage(w http.ResponseWriter, r *http.Request, title s
func (serv *HTTPServer) httpSyscalls(w http.ResponseWriter, r *http.Request) {
var calls map[string]*corpus.CallCov
total := make(map[string]int)
+ fuzzerObj := serv.Fuzzer.Load()
syscallsObj := serv.EnabledSyscalls.Load()
corpusObj := serv.Corpus.Load()
if corpusObj != nil && syscallsObj != nil {
@@ -217,12 +218,23 @@ func (serv *HTTPServer) httpSyscalls(w http.ResponseWriter, r *http.Request) {
if syscall, ok := serv.Cfg.Target.SyscallMap[c]; ok {
syscallID = &syscall.ID
}
+ coverOverflows, compsOverflows := 0, 0
+ if fuzzerObj != nil {
+ idx := len(serv.Cfg.Target.Syscalls)
+ if c != prog.ExtraCallName {
+ idx = serv.Cfg.Target.SyscallMap[c].ID
+ }
+ coverOverflows = int(fuzzerObj.Syscalls[idx].CoverOverflows.Load())
+ compsOverflows = int(fuzzerObj.Syscalls[idx].CompsOverflows.Load())
+ }
data.Calls = append(data.Calls, UICallType{
- Name: c,
- ID: syscallID,
- Inputs: cc.Count,
- Total: total[c],
- Cover: len(cc.Cover),
+ Name: c,
+ ID: syscallID,
+ Inputs: cc.Count,
+ Total: total[c],
+ Cover: len(cc.Cover),
+ CoverOverflows: coverOverflows,
+ CompsOverflows: compsOverflows,
})
}
sort.Slice(data.Calls, func(i, j int) bool {
@@ -702,7 +714,7 @@ func (serv *HTTPServer) httpDebugInput(w http.ResponseWriter, r *http.Request) {
if len(extraIDs) > 0 {
calls = append(calls, UIRawCallCover{
Sig: r.FormValue("sig"),
- Call: ".extra",
+ Call: prog.ExtraCallName,
UpdateIDs: extraIDs,
})
}
@@ -963,11 +975,13 @@ type UIStat struct {
}
type UICallType struct {
- Name string
- ID *int
- Inputs int
- Total int
- Cover int
+ Name string
+ ID *int
+ Inputs int
+ Total int
+ Cover int
+ CoverOverflows int
+ CompsOverflows int
}
type UICorpusPage struct {
diff --git a/prog/prog.go b/prog/prog.go
index de3d87778..ec899e35f 100644
--- a/prog/prog.go
+++ b/prog/prog.go
@@ -17,12 +17,14 @@ type Prog struct {
isUnsafe bool
}
+const ExtraCallName = ".extra"
+
func (p *Prog) CallName(call int) string {
if call >= len(p.Calls) || call < -1 {
panic(fmt.Sprintf("bad call index %v/%v", call, len(p.Calls)))
}
if call == -1 {
- return ".extra"
+ return ExtraCallName
}
return p.Calls[call].Meta.Name
}