Skip to content

Commit

Permalink
cmd/compile, cmd/link: use libFuzzer 8-bit instead of extra counters
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
kyakdan committed May 18, 2022
1 parent 5f2fdbe commit 9057e4b
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 56 deletions.
4 changes: 2 additions & 2 deletions src/cmd/compile/internal/gc/obj.go
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/cmd/compile/internal/ir/name.go
Expand Up @@ -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
)

Expand All @@ -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) }
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/ssa/writebarrier.go
Expand Up @@ -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
}
Expand Down
34 changes: 22 additions & 12 deletions src/cmd/compile/internal/walk/order.go
Expand Up @@ -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 += 1
incr := ir.NewAssignOpStmt(base.Pos, ir.OADD, counter, ir.NewInt(1))
o.append(incr)
counter.Linksym().Type = objabi.SLIBFUZZER_8BIT_COUNTER

// 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,
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/internal/objabi/symkind.go
Expand Up @@ -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.

)
8 changes: 4 additions & 4 deletions src/cmd/internal/objabi/symkind_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions src/cmd/link/internal/ld/data.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/link/internal/ld/elf.go
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/link/internal/ld/xcoff.go
Expand Up @@ -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 {
Expand Down
38 changes: 19 additions & 19 deletions src/cmd/link/internal/sym/symkind.go
Expand Up @@ -97,7 +97,7 @@ const (
SXCOFFTOC
SBSS
SNOPTRBSS
SLIBFUZZER_EXTRA_COUNTER
SLIBFUZZER_8BIT_COUNTER
STLSBSS
SXREF
SMACHOSYMSTR
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/cmd/link/internal/sym/symkind_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 34 additions & 1 deletion src/runtime/libfuzzer.go
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
23 changes: 23 additions & 0 deletions src/runtime/libfuzzer_amd64.s
Expand Up @@ -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
21 changes: 21 additions & 0 deletions src/runtime/libfuzzer_arm64.s
Expand Up @@ -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

0 comments on commit 9057e4b

Please sign in to comment.