Skip to content

Commit fce40f8

Browse files
committed
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).
1 parent c9fe126 commit fce40f8

File tree

17 files changed

+139
-56
lines changed

17 files changed

+139
-56
lines changed

src/cmd/compile/internal/gc/obj.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,8 @@ func ggloblnod(nam *ir.Name) {
281281
flags |= obj.NOPTR
282282
}
283283
base.Ctxt.Globl(s, nam.Type().Size(), flags)
284-
if nam.LibfuzzerExtraCounter() {
285-
s.Type = objabi.SLIBFUZZER_EXTRA_COUNTER
284+
if nam.Libfuzzer8BitCounter() {
285+
s.Type = objabi.SLIBFUZZER_8BIT_COUNTER
286286
}
287287
if nam.Sym().Linkname != "" {
288288
// Make sure linkname'd symbol is non-package. When a symbol is

src/cmd/compile/internal/ir/name.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ const (
266266
nameInlFormal // PAUTO created by inliner, derived from callee formal
267267
nameInlLocal // PAUTO created by inliner, derived from callee local
268268
nameOpenDeferSlot // if temporary var storing info for open-coded defers
269-
nameLibfuzzerExtraCounter // if PEXTERN should be assigned to __libfuzzer_extra_counters section
269+
nameLibfuzzer8BitCounter // if PEXTERN should be assigned to __sancov_cntrs section
270270
nameAlias // is type name an alias
271271
)
272272

@@ -281,7 +281,7 @@ func (n *Name) Addrtaken() bool { return n.flags&nameAddrtaken !=
281281
func (n *Name) InlFormal() bool { return n.flags&nameInlFormal != 0 }
282282
func (n *Name) InlLocal() bool { return n.flags&nameInlLocal != 0 }
283283
func (n *Name) OpenDeferSlot() bool { return n.flags&nameOpenDeferSlot != 0 }
284-
func (n *Name) LibfuzzerExtraCounter() bool { return n.flags&nameLibfuzzerExtraCounter != 0 }
284+
func (n *Name) Libfuzzer8BitCounter() bool { return n.flags&nameLibfuzzer8BitCounter != 0 }
285285

286286
func (n *Name) setReadonly(b bool) { n.flags.set(nameReadonly, b) }
287287
func (n *Name) SetNeedzero(b bool) { n.flags.set(nameNeedzero, b) }
@@ -294,7 +294,7 @@ func (n *Name) SetAddrtaken(b bool) { n.flags.set(nameAddrtaken,
294294
func (n *Name) SetInlFormal(b bool) { n.flags.set(nameInlFormal, b) }
295295
func (n *Name) SetInlLocal(b bool) { n.flags.set(nameInlLocal, b) }
296296
func (n *Name) SetOpenDeferSlot(b bool) { n.flags.set(nameOpenDeferSlot, b) }
297-
func (n *Name) SetLibfuzzerExtraCounter(b bool) { n.flags.set(nameLibfuzzerExtraCounter, b) }
297+
func (n *Name) SetLibfuzzer8BitCounter(b bool) { n.flags.set(nameLibfuzzer8BitCounter, b) }
298298

299299
// OnStack reports whether variable n may reside on the stack.
300300
func (n *Name) OnStack() bool {

src/cmd/compile/internal/ssa/writebarrier.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ func IsSanitizerSafeAddr(v *Value) bool {
650650
// read-only once initialized.
651651
return true
652652
case OpAddr:
653-
return v.Aux.(*obj.LSym).Type == objabi.SRODATA || v.Aux.(*obj.LSym).Type == objabi.SLIBFUZZER_EXTRA_COUNTER
653+
return v.Aux.(*obj.LSym).Type == objabi.SRODATA || v.Aux.(*obj.LSym).Type == objabi.SLIBFUZZER_8BIT_COUNTER
654654
}
655655
return false
656656
}

src/cmd/compile/internal/typecheck/builtin.go

+5-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/compile/internal/typecheck/builtin/runtime.go

+4
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@ func libfuzzerTraceConstCmp1(uint8, uint8)
265265
func libfuzzerTraceConstCmp2(uint16, uint16)
266266
func libfuzzerTraceConstCmp4(uint32, uint32)
267267
func libfuzzerTraceConstCmp8(uint64, uint64)
268+
func libfuzzerIncrementCounter(*uint8)
269+
270+
// This function should be called by the fuzz target on start to register the 8bit counters with libfuzzer
271+
func LibfuzzerInitializeCounters()
268272

269273
// architecture variants
270274
var x86HasPOPCNT bool

src/cmd/compile/internal/walk/order.go

+12-11
Original file line numberDiff line numberDiff line change
@@ -440,21 +440,22 @@ func (o *orderState) edge() {
440440
return
441441
}
442442

443-
// Create a new uint8 counter to be allocated in section
444-
// __libfuzzer_extra_counters.
443+
// Create a new uint8 counter to be allocated in section __sancov_cntrs
445444
counter := staticinit.StaticName(types.Types[types.TUINT8])
446-
counter.SetLibfuzzerExtraCounter(true)
447-
// As well as setting SetLibfuzzerExtraCounter, we preemptively set the
448-
// symbol type to SLIBFUZZER_EXTRA_COUNTER so that the race detector
445+
counter.SetLibfuzzer8BitCounter(true)
446+
// As well as setting SetLibfuzzer8BitCounter, we preemptively set the
447+
// symbol type to SLIBFUZZER_8BIT_COUNTER so that the race detector
449448
// instrumentation pass (which does not have access to the flags set by
450-
// SetLibfuzzerExtraCounter) knows to ignore them. This information is
451-
// lost by the time it reaches the compile step, so SetLibfuzzerExtraCounter
449+
// SetLibfuzzer8BitCounter) knows to ignore them. This information is
450+
// lost by the time it reaches the compile step, so SetLibfuzzer8BitCounter
452451
// is still necessary.
453-
counter.Linksym().Type = objabi.SLIBFUZZER_EXTRA_COUNTER
452+
counter.Linksym().Type = objabi.SLIBFUZZER_8BIT_COUNTER
454453

455-
// counter += 1
456-
incr := ir.NewAssignOpStmt(base.Pos, ir.OADD, counter, ir.NewInt(1))
457-
o.append(incr)
454+
var init ir.Nodes
455+
init.Append(mkcall("libfuzzerIncrementCounter", nil, &init, ir.NewAddrExpr(base.Pos, counter)))
456+
for _, n := range init.Take() {
457+
o.append(n)
458+
}
458459
}
459460

460461
// orderBlock orders the block of statements in n into a new slice,

src/cmd/internal/goobj/builtinlist.go

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/internal/objabi/symkind.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const (
6666
SDWARFLOC
6767
SDWARFLINES
6868
// Coverage instrumentation counter for libfuzzer.
69-
SLIBFUZZER_EXTRA_COUNTER
69+
SLIBFUZZER_8BIT_COUNTER
7070
// Update cmd/link/internal/sym/AbiSymKindToSymKind for new SymKind values.
7171

7272
)

src/cmd/internal/objabi/symkind_string.go

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/link/internal/ld/data.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -1780,10 +1780,10 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
17801780
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.end", 0), sect)
17811781

17821782
// Coverage instrumentation counters for libfuzzer.
1783-
if len(state.data[sym.SLIBFUZZER_EXTRA_COUNTER]) > 0 {
1784-
sect := state.allocateNamedSectionAndAssignSyms(&Segdata, "__libfuzzer_extra_counters", sym.SLIBFUZZER_EXTRA_COUNTER, sym.Sxxx, 06)
1785-
ldr.SetSymSect(ldr.LookupOrCreateSym("internal/fuzz._counters", 0), sect)
1786-
ldr.SetSymSect(ldr.LookupOrCreateSym("internal/fuzz._ecounters", 0), sect)
1783+
if len(state.data[sym.SLIBFUZZER_8BIT_COUNTER]) > 0 {
1784+
sect := state.allocateNamedSectionAndAssignSyms(&Segdata, "__sancov_cntrs", sym.SLIBFUZZER_8BIT_COUNTER, sym.Sxxx, 06)
1785+
ldr.SetSymSect(ldr.LookupOrCreateSym("__start___sancov_cntrs", 0), sect)
1786+
ldr.SetSymSect(ldr.LookupOrCreateSym("__stop___sancov_cntrs", 0), sect)
17871787
}
17881788

17891789
if len(state.data[sym.STLSBSS]) > 0 {
@@ -2561,7 +2561,7 @@ func (ctxt *Link) address() []*sym.Segment {
25612561
bss = s
25622562
case ".noptrbss":
25632563
noptrbss = s
2564-
case "__libfuzzer_extra_counters":
2564+
case "__sancov_cntrs":
25652565
fuzzCounters = s
25662566
}
25672567
}
@@ -2680,8 +2680,8 @@ func (ctxt *Link) address() []*sym.Segment {
26802680
ctxt.xdefine("runtime.end", sym.SBSS, int64(Segdata.Vaddr+Segdata.Length))
26812681

26822682
if fuzzCounters != nil {
2683-
ctxt.xdefine("internal/fuzz._counters", sym.SLIBFUZZER_EXTRA_COUNTER, int64(fuzzCounters.Vaddr))
2684-
ctxt.xdefine("internal/fuzz._ecounters", sym.SLIBFUZZER_EXTRA_COUNTER, int64(fuzzCounters.Vaddr+fuzzCounters.Length))
2683+
ctxt.xdefine("__start___sancov_cntrs", sym.SLIBFUZZER_8BIT_COUNTER, int64(fuzzCounters.Vaddr))
2684+
ctxt.xdefine("__stop___sancov_cntrs", sym.SLIBFUZZER_8BIT_COUNTER, int64(fuzzCounters.Vaddr+fuzzCounters.Length))
26852685
}
26862686

26872687
if ctxt.IsSolaris() {

src/cmd/link/internal/ld/elf.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1294,7 +1294,7 @@ func (ctxt *Link) doelf() {
12941294
shstrtab.Addstring(".data")
12951295
shstrtab.Addstring(".bss")
12961296
shstrtab.Addstring(".noptrbss")
1297-
shstrtab.Addstring("__libfuzzer_extra_counters")
1297+
shstrtab.Addstring("__sancov_cntrs")
12981298
shstrtab.Addstring(".go.buildinfo")
12991299
if ctxt.IsMIPS() {
13001300
shstrtab.Addstring(".MIPS.abiflags")

src/cmd/link/internal/ld/xcoff.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1117,7 +1117,7 @@ func (f *xcoffFile) asmaixsym(ctxt *Link) {
11171117
putaixsym(ctxt, s, TLSSym)
11181118
}
11191119

1120-
case st == sym.SBSS, st == sym.SNOPTRBSS, st == sym.SLIBFUZZER_EXTRA_COUNTER:
1120+
case st == sym.SBSS, st == sym.SNOPTRBSS, st == sym.SLIBFUZZER_8BIT_COUNTER:
11211121
if ldr.AttrReachable(s) {
11221122
data := ldr.Data(s)
11231123
if len(data) > 0 {

src/cmd/link/internal/sym/symkind.go

+19-19
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ const (
9696
SXCOFFTOC
9797
SBSS
9898
SNOPTRBSS
99-
SLIBFUZZER_EXTRA_COUNTER
99+
SLIBFUZZER_8BIT_COUNTER
100100
STLSBSS
101101
SXREF
102102
SMACHOSYMSTR
@@ -125,24 +125,24 @@ const (
125125
// AbiSymKindToSymKind maps values read from object files (which are
126126
// of type cmd/internal/objabi.SymKind) to values of type SymKind.
127127
var AbiSymKindToSymKind = [...]SymKind{
128-
objabi.Sxxx: Sxxx,
129-
objabi.STEXT: STEXT,
130-
objabi.SRODATA: SRODATA,
131-
objabi.SNOPTRDATA: SNOPTRDATA,
132-
objabi.SDATA: SDATA,
133-
objabi.SBSS: SBSS,
134-
objabi.SNOPTRBSS: SNOPTRBSS,
135-
objabi.STLSBSS: STLSBSS,
136-
objabi.SDWARFCUINFO: SDWARFCUINFO,
137-
objabi.SDWARFCONST: SDWARFCONST,
138-
objabi.SDWARFFCN: SDWARFFCN,
139-
objabi.SDWARFABSFCN: SDWARFABSFCN,
140-
objabi.SDWARFTYPE: SDWARFTYPE,
141-
objabi.SDWARFVAR: SDWARFVAR,
142-
objabi.SDWARFRANGE: SDWARFRANGE,
143-
objabi.SDWARFLOC: SDWARFLOC,
144-
objabi.SDWARFLINES: SDWARFLINES,
145-
objabi.SLIBFUZZER_EXTRA_COUNTER: SLIBFUZZER_EXTRA_COUNTER,
128+
objabi.Sxxx: Sxxx,
129+
objabi.STEXT: STEXT,
130+
objabi.SRODATA: SRODATA,
131+
objabi.SNOPTRDATA: SNOPTRDATA,
132+
objabi.SDATA: SDATA,
133+
objabi.SBSS: SBSS,
134+
objabi.SNOPTRBSS: SNOPTRBSS,
135+
objabi.STLSBSS: STLSBSS,
136+
objabi.SDWARFCUINFO: SDWARFCUINFO,
137+
objabi.SDWARFCONST: SDWARFCONST,
138+
objabi.SDWARFFCN: SDWARFFCN,
139+
objabi.SDWARFABSFCN: SDWARFABSFCN,
140+
objabi.SDWARFTYPE: SDWARFTYPE,
141+
objabi.SDWARFVAR: SDWARFVAR,
142+
objabi.SDWARFRANGE: SDWARFRANGE,
143+
objabi.SDWARFLOC: SDWARFLOC,
144+
objabi.SDWARFLINES: SDWARFLINES,
145+
objabi.SLIBFUZZER_8BIT_COUNTER: SLIBFUZZER_8BIT_COUNTER,
146146
}
147147

148148
// ReadOnly are the symbol kinds that form read-only sections. In some

src/cmd/link/internal/sym/symkind_string.go

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/internal/fuzz/trace.go

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import _ "unsafe" // for go:linkname
1818
//go:linkname libfuzzerTraceConstCmp4 runtime.libfuzzerTraceConstCmp4
1919
//go:linkname libfuzzerTraceConstCmp8 runtime.libfuzzerTraceConstCmp8
2020

21+
//go:linkname libfuzzerIncrementCounter runtime.libfuzzerIncrementCounter
22+
2123
func libfuzzerTraceCmp1(arg0, arg1 uint8) {}
2224
func libfuzzerTraceCmp2(arg0, arg1 uint16) {}
2325
func libfuzzerTraceCmp4(arg0, arg1 uint32) {}
@@ -27,3 +29,5 @@ func libfuzzerTraceConstCmp1(arg0, arg1 uint8) {}
2729
func libfuzzerTraceConstCmp2(arg0, arg1 uint16) {}
2830
func libfuzzerTraceConstCmp4(arg0, arg1 uint32) {}
2931
func libfuzzerTraceConstCmp8(arg0, arg1 uint64) {}
32+
33+
func libfuzzerIncrementCounter(counter *uint8) {}

src/runtime/libfuzzer.go

+46-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

77
package runtime
88

9-
import _ "unsafe" // for go:linkname
9+
import "unsafe"
1010

11+
func libfuzzerCallTraceInit(fn, start, end *byte)
1112
func libfuzzerCall(fn *byte, arg0, arg1 uintptr)
1213

1314
func libfuzzerTraceCmp1(arg0, arg1 uint8) {
@@ -42,6 +43,34 @@ func libfuzzerTraceConstCmp8(arg0, arg1 uint64) {
4243
libfuzzerCall(&__sanitizer_cov_trace_const_cmp8, uintptr(arg0), uintptr(arg1))
4344
}
4445

46+
var pcTables []byte
47+
48+
func LibfuzzerInitializeCounters() {
49+
libfuzzerCallTraceInit(&__sanitizer_cov_8bit_counters_init, &__start___sancov_cntrs, &__stop___sancov_cntrs)
50+
start := unsafe.Pointer(&__start___sancov_cntrs)
51+
end := unsafe.Pointer(&__stop___sancov_cntrs)
52+
53+
// PC tables are arrays of ptr-sized integers representing pairs [PC,PCFlags] for every instrumented block.
54+
// The number of PCs and PCFlags is the same as the number of 8-bit counters. Each PC table entry has
55+
// the size of two ptr-sized integers. We allocate one more byte than what we actually need so that we can
56+
// get a pointer representing the end of the PC table array.
57+
size := (uintptr(end)-uintptr(start))*unsafe.Sizeof(uintptr(0))*2 + 1
58+
pcTables = make([]byte, size)
59+
libfuzzerCallTraceInit(&__sanitizer_cov_pcs_init, &pcTables[0], &pcTables[size-1])
60+
}
61+
62+
// libfuzzerIncrementCounter guarantees that the counter never becomes zero
63+
// again once it has been incremented once. It implements the NeverZero
64+
// optimization presented by the paper:
65+
// "AFL++: Combining Incremental Steps of Fuzzing Research"
66+
func libfuzzerIncrementCounter(counter *uint8) {
67+
if *counter == 0xff {
68+
*counter = 1
69+
} else {
70+
*counter++
71+
}
72+
}
73+
4574
//go:linkname __sanitizer_cov_trace_cmp1 __sanitizer_cov_trace_cmp1
4675
//go:cgo_import_static __sanitizer_cov_trace_cmp1
4776
var __sanitizer_cov_trace_cmp1 byte
@@ -73,3 +102,19 @@ var __sanitizer_cov_trace_const_cmp4 byte
73102
//go:linkname __sanitizer_cov_trace_const_cmp8 __sanitizer_cov_trace_const_cmp8
74103
//go:cgo_import_static __sanitizer_cov_trace_const_cmp8
75104
var __sanitizer_cov_trace_const_cmp8 byte
105+
106+
//go:linkname __sanitizer_cov_8bit_counters_init __sanitizer_cov_8bit_counters_init
107+
//go:cgo_import_static __sanitizer_cov_8bit_counters_init
108+
var __sanitizer_cov_8bit_counters_init byte
109+
110+
//go:linkname __start___sancov_cntrs __start___sancov_cntrs
111+
//go:cgo_import_static __start___sancov_cntrs
112+
var __start___sancov_cntrs byte
113+
114+
//go:linkname __stop___sancov_cntrs __stop___sancov_cntrs
115+
//go:cgo_import_static __stop___sancov_cntrs
116+
var __stop___sancov_cntrs byte
117+
118+
//go:linkname __sanitizer_cov_pcs_init __sanitizer_cov_pcs_init
119+
//go:cgo_import_static __sanitizer_cov_pcs_init
120+
var __sanitizer_cov_pcs_init byte

src/runtime/libfuzzer_amd64.s

+23
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,26 @@ call:
4040
CALL AX
4141
MOVQ R12, SP
4242
RET
43+
44+
// void runtime·libfuzzerCallTraceInit(fn, arg0, arg1 *byte)
45+
// Calls C function fn from libFuzzer and passes 2 arguments to it.
46+
TEXT runtime·libfuzzerCallTraceInit(SB), NOSPLIT, $0-24
47+
MOVQ fn+0(FP), AX
48+
MOVQ start+8(FP), RARG0
49+
MOVQ end+16(FP), RARG1
50+
51+
get_tls(R12)
52+
MOVQ g(R12), R14
53+
MOVQ g_m(R14), R13
54+
55+
// Switch to g0 stack.
56+
MOVQ SP, R12 // callee-saved, preserved across the CALL
57+
MOVQ m_g0(R13), R10
58+
CMPQ R10, R14
59+
JE call // already on g0
60+
MOVQ (g_sched+gobuf_sp)(R10), SP
61+
call:
62+
ANDQ $~15, SP // alignment for gcc ABI
63+
CALL AX
64+
MOVQ R12, SP
65+
RET

0 commit comments

Comments
 (0)