cmd/compile, cmd/link: use libFuzzer 8-bit instead of extra counters

By using libFuzzer’s 8-bit counters instead of extra counters, the
coverage instrumentation in libFuzzer mode is improved in three ways:
  1- 8-bit counters are supported on all platforms, including macOS and
     Windows, with all relevant versions of libFuzzer, whereas extra
     counters are a Linux-only feature that only recently received
     support on Windows.
  2- Newly covered blocks are now properly reported as new coverage by
     libFuzzer, not only as new features.
  3- The NeverZero strategy is used to ensure that coverage counters
     never become 0 again after having been positive once. This resolves
     issues encountered when fuzzing loops with iteration counts that
     are multiples of 256 (e.g., larger powers of two).
This commit is contained in:
Khaled Yakdan 2022-02-22 10:47:38 +01:00
parent 5f2fdbe7ed
commit 9057e4b21d
14 changed files with 142 additions and 55 deletions

View file

@ -313,8 +313,8 @@ func ggloblnod(nam *ir.Name) {
} else {
base.Ctxt.Globl(s, size, flags)
}
if nam.LibfuzzerExtraCounter() {
s.Type = objabi.SLIBFUZZER_EXTRA_COUNTER
if nam.Libfuzzer8BitCounter() {
s.Type = objabi.SLIBFUZZER_8BIT_COUNTER
}
if nam.Sym().Linkname != "" {
// Make sure linkname'd symbol is non-package. When a symbol is

View file

@ -235,7 +235,7 @@ const (
nameInlFormal // PAUTO created by inliner, derived from callee formal
nameInlLocal // PAUTO created by inliner, derived from callee local
nameOpenDeferSlot // if temporary var storing info for open-coded defers
nameLibfuzzerExtraCounter // if PEXTERN should be assigned to __libfuzzer_extra_counters section
nameLibfuzzer8BitCounter // if PEXTERN should be assigned to __sancov_cntrs section
nameAlias // is type name an alias
)
@ -250,7 +250,7 @@ func (n *Name) Addrtaken() bool { return n.flags&nameAddrtaken !=
func (n *Name) InlFormal() bool { return n.flags&nameInlFormal != 0 }
func (n *Name) InlLocal() bool { return n.flags&nameInlLocal != 0 }
func (n *Name) OpenDeferSlot() bool { return n.flags&nameOpenDeferSlot != 0 }
func (n *Name) LibfuzzerExtraCounter() bool { return n.flags&nameLibfuzzerExtraCounter != 0 }
func (n *Name) Libfuzzer8BitCounter() bool { return n.flags&nameLibfuzzer8BitCounter != 0 }
func (n *Name) setReadonly(b bool) { n.flags.set(nameReadonly, b) }
func (n *Name) SetNeedzero(b bool) { n.flags.set(nameNeedzero, b) }
@ -263,7 +263,7 @@ func (n *Name) SetAddrtaken(b bool) { n.flags.set(nameAddrtaken,
func (n *Name) SetInlFormal(b bool) { n.flags.set(nameInlFormal, b) }
func (n *Name) SetInlLocal(b bool) { n.flags.set(nameInlLocal, b) }
func (n *Name) SetOpenDeferSlot(b bool) { n.flags.set(nameOpenDeferSlot, b) }
func (n *Name) SetLibfuzzerExtraCounter(b bool) { n.flags.set(nameLibfuzzerExtraCounter, b) }
func (n *Name) SetLibfuzzer8BitCounter(b bool) { n.flags.set(nameLibfuzzer8BitCounter, b) }
// OnStack reports whether variable n may reside on the stack.
func (n *Name) OnStack() bool {

View file

@ -658,7 +658,7 @@ func IsSanitizerSafeAddr(v *Value) bool {
// read-only once initialized.
return true
case OpAddr:
return v.Aux.(*obj.LSym).Type == objabi.SRODATA || v.Aux.(*obj.LSym).Type == objabi.SLIBFUZZER_EXTRA_COUNTER
return v.Aux.(*obj.LSym).Type == objabi.SRODATA || v.Aux.(*obj.LSym).Type == objabi.SLIBFUZZER_8BIT_COUNTER
}
return false
}

View file

@ -446,21 +446,31 @@ func (o *orderState) edge() {
return
}
// Create a new uint8 counter to be allocated in section
// __libfuzzer_extra_counters.
// Create a new uint8 counter to be allocated in section __sancov_cntrs
counter := staticinit.StaticName(types.Types[types.TUINT8])
counter.SetLibfuzzerExtraCounter(true)
// As well as setting SetLibfuzzerExtraCounter, we preemptively set the
// symbol type to SLIBFUZZER_EXTRA_COUNTER so that the race detector
counter.SetLibfuzzer8BitCounter(true)
// As well as setting SetLibfuzzer8BitCounter, we preemptively set the
// symbol type to SLIBFUZZER_8BIT_COUNTER so that the race detector
// instrumentation pass (which does not have access to the flags set by
// SetLibfuzzerExtraCounter) knows to ignore them. This information is
// lost by the time it reaches the compile step, so SetLibfuzzerExtraCounter
// SetLibfuzzer8BitCounter) knows to ignore them. This information is
// lost by the time it reaches the compile step, so SetLibfuzzer8BitCounter
// is still necessary.
counter.Linksym().Type = objabi.SLIBFUZZER_EXTRA_COUNTER
counter.Linksym().Type = objabi.SLIBFUZZER_8BIT_COUNTER
// counter += 1
incr := ir.NewAssignOpStmt(base.Pos, ir.OADD, counter, ir.NewInt(1))
o.append(incr)
// We guarantee that the counter never becomes zero again once it has been
// incremented once. This implementation follows the NeverZero optimization
// presented by the paper:
// "AFL++: Combining Incremental Steps of Fuzzing Research"
// The NeverZero policy avoids the overflow to 0 by setting the counter to one
// after it reaches 255 and so, if an edge is executed at least one time, the entry is
// never 0.
// Another policy presented in the paper is the Saturated Counters policy which
// freezes the counter when it reaches the value of 255. However, a range
// of experiments showed that that decreases overall performance.
o.append(ir.NewIfStmt(base.Pos,
ir.NewBinaryExpr(base.Pos, ir.OEQ, counter, ir.NewInt(0xff)),
[]ir.Node{ir.NewAssignStmt(base.Pos, counter, ir.NewInt(1))},
[]ir.Node{ir.NewAssignOpStmt(base.Pos, ir.OADD, counter, ir.NewInt(1))}))
}
// orderBlock orders the block of statements in n into a new slice,

View file

@ -67,7 +67,7 @@ const (
SDWARFLOC
SDWARFLINES
// Coverage instrumentation counter for libfuzzer.
SLIBFUZZER_EXTRA_COUNTER
SLIBFUZZER_8BIT_COUNTER
// Update cmd/link/internal/sym/AbiSymKindToSymKind for new SymKind values.
)

View file

@ -1,4 +1,4 @@
// Code generated by "stringer -type=SymKind"; DO NOT EDIT.
// Code generated by "stringer -type=SymKind symkind.go"; DO NOT EDIT.
package objabi
@ -25,12 +25,12 @@ func _() {
_ = x[SDWARFRANGE-14]
_ = x[SDWARFLOC-15]
_ = x[SDWARFLINES-16]
_ = x[SLIBFUZZER_EXTRA_COUNTER-17]
_ = x[SLIBFUZZER_8BIT_COUNTER-17]
}
const _SymKind_name = "SxxxSTEXTSRODATASNOPTRDATASDATASBSSSNOPTRBSSSTLSBSSSDWARFCUINFOSDWARFCONSTSDWARFFCNSDWARFABSFCNSDWARFTYPESDWARFVARSDWARFRANGESDWARFLOCSDWARFLINESSLIBFUZZER_EXTRA_COUNTER"
const _SymKind_name = "SxxxSTEXTSRODATASNOPTRDATASDATASBSSSNOPTRBSSSTLSBSSSDWARFCUINFOSDWARFCONSTSDWARFFCNSDWARFABSFCNSDWARFTYPESDWARFVARSDWARFRANGESDWARFLOCSDWARFLINESSLIBFUZZER_8BIT_COUNTER"
var _SymKind_index = [...]uint8{0, 4, 9, 16, 26, 31, 35, 44, 51, 63, 74, 83, 95, 105, 114, 125, 134, 145, 169}
var _SymKind_index = [...]uint8{0, 4, 9, 16, 26, 31, 35, 44, 51, 63, 74, 83, 95, 105, 114, 125, 134, 145, 168}
func (i SymKind) String() string {
if i >= SymKind(len(_SymKind_index)-1) {

View file

@ -1782,10 +1782,10 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.end", 0), sect)
// Coverage instrumentation counters for libfuzzer.
if len(state.data[sym.SLIBFUZZER_EXTRA_COUNTER]) > 0 {
sect := state.allocateNamedSectionAndAssignSyms(&Segdata, "__libfuzzer_extra_counters", sym.SLIBFUZZER_EXTRA_COUNTER, sym.Sxxx, 06)
ldr.SetSymSect(ldr.LookupOrCreateSym("internal/fuzz._counters", 0), sect)
ldr.SetSymSect(ldr.LookupOrCreateSym("internal/fuzz._ecounters", 0), sect)
if len(state.data[sym.SLIBFUZZER_8BIT_COUNTER]) > 0 {
sect := state.allocateNamedSectionAndAssignSyms(&Segdata, "__sancov_cntrs", sym.SLIBFUZZER_8BIT_COUNTER, sym.Sxxx, 06)
ldr.SetSymSect(ldr.LookupOrCreateSym("__start___sancov_cntrs", 0), sect)
ldr.SetSymSect(ldr.LookupOrCreateSym("__stop___sancov_cntrs", 0), sect)
}
if len(state.data[sym.STLSBSS]) > 0 {
@ -2558,7 +2558,7 @@ func (ctxt *Link) address() []*sym.Segment {
bss = s
case ".noptrbss":
noptrbss = s
case "__libfuzzer_extra_counters":
case "__sancov_cntrs":
fuzzCounters = s
}
}
@ -2677,8 +2677,8 @@ func (ctxt *Link) address() []*sym.Segment {
ctxt.xdefine("runtime.end", sym.SBSS, int64(Segdata.Vaddr+Segdata.Length))
if fuzzCounters != nil {
ctxt.xdefine("internal/fuzz._counters", sym.SLIBFUZZER_EXTRA_COUNTER, int64(fuzzCounters.Vaddr))
ctxt.xdefine("internal/fuzz._ecounters", sym.SLIBFUZZER_EXTRA_COUNTER, int64(fuzzCounters.Vaddr+fuzzCounters.Length))
ctxt.xdefine("__start___sancov_cntrs", sym.SLIBFUZZER_8BIT_COUNTER, int64(fuzzCounters.Vaddr))
ctxt.xdefine("__stop___sancov_cntrs", sym.SLIBFUZZER_8BIT_COUNTER, int64(fuzzCounters.Vaddr+fuzzCounters.Length))
}
if ctxt.IsSolaris() {

View file

@ -1304,7 +1304,7 @@ func (ctxt *Link) doelf() {
shstrtab.Addstring(".data")
shstrtab.Addstring(".bss")
shstrtab.Addstring(".noptrbss")
shstrtab.Addstring("__libfuzzer_extra_counters")
shstrtab.Addstring("__sancov_cntrs")
shstrtab.Addstring(".go.buildinfo")
if ctxt.IsMIPS() {
shstrtab.Addstring(".MIPS.abiflags")

View file

@ -1117,7 +1117,7 @@ func (f *xcoffFile) asmaixsym(ctxt *Link) {
putaixsym(ctxt, s, TLSSym)
}
case st == sym.SBSS, st == sym.SNOPTRBSS, st == sym.SLIBFUZZER_EXTRA_COUNTER:
case st == sym.SBSS, st == sym.SNOPTRBSS, st == sym.SLIBFUZZER_8BIT_COUNTER:
if ldr.AttrReachable(s) {
data := ldr.Data(s)
if len(data) > 0 {

View file

@ -97,7 +97,7 @@ const (
SXCOFFTOC
SBSS
SNOPTRBSS
SLIBFUZZER_EXTRA_COUNTER
SLIBFUZZER_8BIT_COUNTER
STLSBSS
SXREF
SMACHOSYMSTR
@ -126,24 +126,24 @@ const (
// AbiSymKindToSymKind maps values read from object files (which are
// of type cmd/internal/objabi.SymKind) to values of type SymKind.
var AbiSymKindToSymKind = [...]SymKind{
objabi.Sxxx: Sxxx,
objabi.STEXT: STEXT,
objabi.SRODATA: SRODATA,
objabi.SNOPTRDATA: SNOPTRDATA,
objabi.SDATA: SDATA,
objabi.SBSS: SBSS,
objabi.SNOPTRBSS: SNOPTRBSS,
objabi.STLSBSS: STLSBSS,
objabi.SDWARFCUINFO: SDWARFCUINFO,
objabi.SDWARFCONST: SDWARFCONST,
objabi.SDWARFFCN: SDWARFFCN,
objabi.SDWARFABSFCN: SDWARFABSFCN,
objabi.SDWARFTYPE: SDWARFTYPE,
objabi.SDWARFVAR: SDWARFVAR,
objabi.SDWARFRANGE: SDWARFRANGE,
objabi.SDWARFLOC: SDWARFLOC,
objabi.SDWARFLINES: SDWARFLINES,
objabi.SLIBFUZZER_EXTRA_COUNTER: SLIBFUZZER_EXTRA_COUNTER,
objabi.Sxxx: Sxxx,
objabi.STEXT: STEXT,
objabi.SRODATA: SRODATA,
objabi.SNOPTRDATA: SNOPTRDATA,
objabi.SDATA: SDATA,
objabi.SBSS: SBSS,
objabi.SNOPTRBSS: SNOPTRBSS,
objabi.STLSBSS: STLSBSS,
objabi.SDWARFCUINFO: SDWARFCUINFO,
objabi.SDWARFCONST: SDWARFCONST,
objabi.SDWARFFCN: SDWARFFCN,
objabi.SDWARFABSFCN: SDWARFABSFCN,
objabi.SDWARFTYPE: SDWARFTYPE,
objabi.SDWARFVAR: SDWARFVAR,
objabi.SDWARFRANGE: SDWARFRANGE,
objabi.SDWARFLOC: SDWARFLOC,
objabi.SDWARFLINES: SDWARFLINES,
objabi.SLIBFUZZER_8BIT_COUNTER: SLIBFUZZER_8BIT_COUNTER,
}
// ReadOnly are the symbol kinds that form read-only sections. In some

View file

@ -1,4 +1,4 @@
// Code generated by "stringer -type=SymKind"; DO NOT EDIT.
// Code generated by "stringer -type=SymKind symkind.go"; DO NOT EDIT.
package sym
@ -44,7 +44,7 @@ func _() {
_ = x[SXCOFFTOC-33]
_ = x[SBSS-34]
_ = x[SNOPTRBSS-35]
_ = x[SLIBFUZZER_EXTRA_COUNTER-36]
_ = x[SLIBFUZZER_8BIT_COUNTER-36]
_ = x[STLSBSS-37]
_ = x[SXREF-38]
_ = x[SMACHOSYMSTR-39]
@ -67,9 +67,9 @@ func _() {
_ = x[SDWARFLINES-56]
}
const _SymKind_name = "SxxxSTEXTSELFRXSECTSMACHOPLTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASFUNCTABSELFROSECTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSTYPELINKSITABLINKSSYMTABSPCLNTABSFirstWritableSBUILDINFOSELFSECTSMACHOSMACHOGOTSWINDOWSSELFGOTSNOPTRDATASINITARRSDATASXCOFFTOCSBSSSNOPTRBSSSLIBFUZZER_EXTRA_COUNTERSTLSBSSSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSFILEPATHSDYNIMPORTSHOSTOBJSUNDEFEXTSDWARFSECTSDWARFCUINFOSDWARFCONSTSDWARFFCNSDWARFABSFCNSDWARFTYPESDWARFVARSDWARFRANGESDWARFLOCSDWARFLINES"
const _SymKind_name = "SxxxSTEXTSELFRXSECTSMACHOPLTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASFUNCTABSELFROSECTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSTYPELINKSITABLINKSSYMTABSPCLNTABSFirstWritableSBUILDINFOSELFSECTSMACHOSMACHOGOTSWINDOWSSELFGOTSNOPTRDATASINITARRSDATASXCOFFTOCSBSSSNOPTRBSSSLIBFUZZER_8BIT_COUNTERSTLSBSSSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSFILEPATHSDYNIMPORTSHOSTOBJSUNDEFEXTSDWARFSECTSDWARFCUINFOSDWARFCONSTSDWARFFCNSDWARFABSFCNSDWARFTYPESDWARFVARSDWARFRANGESDWARFLOCSDWARFLINES"
var _SymKind_index = [...]uint16{0, 4, 9, 19, 28, 33, 40, 49, 56, 63, 70, 78, 88, 98, 110, 124, 136, 148, 160, 173, 182, 191, 198, 206, 220, 230, 238, 244, 253, 261, 268, 278, 286, 291, 300, 304, 313, 337, 344, 349, 361, 373, 390, 407, 416, 426, 434, 443, 453, 465, 476, 485, 497, 507, 516, 527, 536, 547}
var _SymKind_index = [...]uint16{0, 4, 9, 19, 28, 33, 40, 49, 56, 63, 70, 78, 88, 98, 110, 124, 136, 148, 160, 173, 182, 191, 198, 206, 220, 230, 238, 244, 253, 261, 268, 278, 286, 291, 300, 304, 313, 336, 343, 348, 360, 372, 389, 406, 415, 425, 433, 442, 452, 464, 475, 484, 496, 506, 515, 526, 535, 546}
func (i SymKind) String() string {
if i >= SymKind(len(_SymKind_index)-1) {

View file

@ -6,8 +6,9 @@
package runtime
import _ "unsafe" // for go:linkname
import "unsafe"
func libfuzzerCallWithTwoByteBuffers(fn, start, end *byte)
func libfuzzerCall(fn *byte, arg0, arg1 uintptr)
func libfuzzerTraceCmp1(arg0, arg1 uint8) {
@ -42,6 +43,22 @@ func libfuzzerTraceConstCmp8(arg0, arg1 uint64) {
libfuzzerCall(&__sanitizer_cov_trace_const_cmp8, uintptr(arg0), uintptr(arg1))
}
var pcTables []byte
func init() {
libfuzzerCallWithTwoByteBuffers(&__sanitizer_cov_8bit_counters_init, &__start___sancov_cntrs, &__stop___sancov_cntrs)
start := unsafe.Pointer(&__start___sancov_cntrs)
end := unsafe.Pointer(&__stop___sancov_cntrs)
// PC tables are arrays of ptr-sized integers representing pairs [PC,PCFlags] for every instrumented block.
// The number of PCs and PCFlags is the same as the number of 8-bit counters. Each PC table entry has
// the size of two ptr-sized integers. We allocate one more byte than what we actually need so that we can
// get a pointer representing the end of the PC table array.
size := (uintptr(end)-uintptr(start))*unsafe.Sizeof(uintptr(0))*2 + 1
pcTables = make([]byte, size)
libfuzzerCallWithTwoByteBuffers(&__sanitizer_cov_pcs_init, &pcTables[0], &pcTables[size-1])
}
//go:linkname __sanitizer_cov_trace_cmp1 __sanitizer_cov_trace_cmp1
//go:cgo_import_static __sanitizer_cov_trace_cmp1
var __sanitizer_cov_trace_cmp1 byte
@ -73,3 +90,19 @@ var __sanitizer_cov_trace_const_cmp4 byte
//go:linkname __sanitizer_cov_trace_const_cmp8 __sanitizer_cov_trace_const_cmp8
//go:cgo_import_static __sanitizer_cov_trace_const_cmp8
var __sanitizer_cov_trace_const_cmp8 byte
//go:linkname __sanitizer_cov_8bit_counters_init __sanitizer_cov_8bit_counters_init
//go:cgo_import_static __sanitizer_cov_8bit_counters_init
var __sanitizer_cov_8bit_counters_init byte
//go:linkname __start___sancov_cntrs __start___sancov_cntrs
//go:cgo_import_static __start___sancov_cntrs
var __start___sancov_cntrs byte
//go:linkname __stop___sancov_cntrs __stop___sancov_cntrs
//go:cgo_import_static __stop___sancov_cntrs
var __stop___sancov_cntrs byte
//go:linkname __sanitizer_cov_pcs_init __sanitizer_cov_pcs_init
//go:cgo_import_static __sanitizer_cov_pcs_init
var __sanitizer_cov_pcs_init byte

View file

@ -40,3 +40,26 @@ call:
CALL AX
MOVQ R12, SP
RET
// void runtime·libfuzzerCallWithTwoByteBuffers(fn, start, end *byte)
// Calls C function fn from libFuzzer and passes 2 arguments of type *byte to it.
TEXT runtime·libfuzzerCallWithTwoByteBuffers(SB), NOSPLIT, $0-24
MOVQ fn+0(FP), AX
MOVQ start+8(FP), RARG0
MOVQ end+16(FP), RARG1
get_tls(R12)
MOVQ g(R12), R14
MOVQ g_m(R14), R13
// Switch to g0 stack.
MOVQ SP, R12 // callee-saved, preserved across the CALL
MOVQ m_g0(R13), R10
CMPQ R10, R14
JE call // already on g0
MOVQ (g_sched+gobuf_sp)(R10), SP
call:
ANDQ $~15, SP // alignment for gcc ABI
CALL AX
MOVQ R12, SP
RET

View file

@ -29,3 +29,24 @@ call:
BL R9
MOVD R19, RSP
RET
// void runtime·libfuzzerCallWithTwoByteBuffers(fn, start, end *byte)
// Calls C function fn from libFuzzer and passes 2 arguments of type *byte to it.
TEXT runtime·libfuzzerCallWithTwoByteBuffers(SB), NOSPLIT, $0-24
MOVD fn+0(FP), R9
MOVD start+8(FP), R0
MOVD end+16(FP), R1
MOVD g_m(g), R10
// Switch to g0 stack.
MOVD RSP, R19 // callee-saved, preserved across the CALL
MOVD m_g0(R10), R11
CMP R11, g
BEQ call // already on g0
MOVD (g_sched+gobuf_sp)(R11), R12
MOVD R12, RSP
call:
BL R9
MOVD R19, RSP
RET