From 89b7886c44814bf3f37e59fa6c0749263955d126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20L=C3=B3pez=20Jaimez?= Date: Fri, 24 Oct 2025 15:42:54 +0000 Subject: [PATCH 1/2] update the random generator to return more robust randomness --- ebpf_ffi/ffi.cc | 3 +- pkg/cbpf/instruction_generators.go | 2 +- pkg/ebpf/instruction_generators.go | 6 +- pkg/rand/rand.go | 117 ++++++++++++++++------------- 4 files changed, 70 insertions(+), 58 deletions(-) diff --git a/ebpf_ffi/ffi.cc b/ebpf_ffi/ffi.cc index d46e28e..426c9a9 100644 --- a/ebpf_ffi/ffi.cc +++ b/ebpf_ffi/ffi.cc @@ -122,7 +122,8 @@ int ffi_cleanup_coverage() { if (!kCoverageData) return 0; close(kCoverageData->fd); - munmap(kCoverageData->coverage_buffer, KCOV_SIZE * sizeof(uint64_t)); + if (kCoverageData->coverage_buffer != nullptr) + munmap(kCoverageData->coverage_buffer, KCOV_SIZE * sizeof(uint64_t)); free(kCoverageData); kCoverageData = nullptr; return 0; diff --git a/pkg/cbpf/instruction_generators.go b/pkg/cbpf/instruction_generators.go index 2faa4ba..04051c5 100644 --- a/pkg/cbpf/instruction_generators.go +++ b/pkg/cbpf/instruction_generators.go @@ -54,7 +54,7 @@ func RandomJmpInstruction(maxOffset uint64) *pb.Instruction { } offset := int8(rand.SharedRNG.RandRange(1, maxOffset)) - if rand.SharedRNG.OneOf(2) { + if rand.SharedRNG.RandBool() { src := int32(rand.SharedRNG.RandRange(0, 0xffffffff)) return newJmpInstruction(op, 0, int32(offset), src) } else { diff --git a/pkg/ebpf/instruction_generators.go b/pkg/ebpf/instruction_generators.go index 8a7a4b0..46e7ffd 100644 --- a/pkg/ebpf/instruction_generators.go +++ b/pkg/ebpf/instruction_generators.go @@ -60,7 +60,7 @@ func RandomJmpInstruction(maxOffset uint64) *pb.Instruction { } var insClass pb.InsClass - if rand.SharedRNG.OneOf(2) { + if rand.SharedRNG.RandBool() { insClass = pb.InsClass_InsClassJmp32 } else { insClass = pb.InsClass_InsClassJmp @@ -68,7 +68,7 @@ func RandomJmpInstruction(maxOffset uint64) *pb.Instruction { dstReg := RandomRegister() offset := int16(rand.SharedRNG.RandRange(1, maxOffset)) - if rand.SharedRNG.OneOf(2) { + if rand.SharedRNG.RandBool() { src := int32(rand.SharedRNG.RandRange(0, 0xffffffff)) return newJmpInstruction(op, insClass, dstReg, src, offset) } else { @@ -166,7 +166,7 @@ func RandomStoreInstruction() *pb.Instruction { offset := RandomOffset(size) // Decide if we are doing a Store from a register or a constant. - if rand.SharedRNG.OneOf(2) { + if rand.SharedRNG.RandBool() { // Constant imm := int32(rand.SharedRNG.RandInt()) return newStoreOperation(size, R10, imm, offset) diff --git a/pkg/rand/rand.go b/pkg/rand/rand.go index a7cbe2d..85ac1da 100644 --- a/pkg/rand/rand.go +++ b/pkg/rand/rand.go @@ -17,80 +17,91 @@ // It also provides utilities for randomly making a choice. // Last but not least, it contains a wrapper struct with a randomness source associated with it // to ensure that each fuzzing instance generates unique inputs and has a unique seed. + package rand import ( + crypto_rand "crypto/rand" // Aliased to avoid conflict + "encoding/binary" "math/rand" - "time" + "sync" ) -var ( - // Some potentially interesting integers - These represent maximum and minimum - // values for various integer sizes and values that tend to lead to overflow issues - specialInts = []uint64{ - 0, 1, 31, 32, 63, 64, 127, 128, - 129, 255, 256, 257, 511, 512, - 1023, 1024, 1025, 2047, 2048, 4095, 4096, - (1 << 15) - 1, (1 << 15), (1 << 15) + 1, - (1 << 16) - 1, (1 << 16), (1 << 16) + 1, - (1 << 31) - 1, (1 << 31), (1 << 31) + 1, - (1 << 32) - 1, (1 << 32), (1 << 32) + 1, - (1 << 63) - 1, (1 << 63), (1 << 63) + 1, - (1 << 64) - 1, +// newSeed generates a cryptographically secure 64-bit seed. +// It panics if it can't read from crypto/rand. +func newSeed() int64 { + var b [8]byte + if _, err := crypto_rand.Read(b[:]); err != nil { + panic("cannot seed math/rand: " + err.Error()) } -) + return int64(binary.BigEndian.Uint64(b[:])) +} -// NumGen provides helper methods for generating random integers. Each instance has its own seed -// to prevent concurrent VMs from generating the same inputs -type NumGen struct { - r *rand.Rand +// BuzzerRNG wraps a math/rand.Rand generator and its mutex +// to make it thread-safe. +type BuzzerRNG struct { + r *rand.Rand + mu sync.Mutex } -// NewRand generates a new random number generator -func NewRand(randSource rand.Source) *NumGen { - return &NumGen{ - r: rand.New(randSource), +// New returns a new, securely seeded, thread-safe BuzzerRNG. +func New() *BuzzerRNG { + return &BuzzerRNG{ + r: rand.New(rand.NewSource(newSeed())), } } -var SharedRNG = NewRand(rand.NewSource(time.Now().Unix())) +// SharedRNG is the default, package-level singleton generator. +// It is an instance of BuzzerRNG and is safe for concurrent use. +var SharedRNG = New() -// RandRange returns a random 64-bit integer in the range of begin..end -func (g *NumGen) RandRange(begin, end uint64) uint64 { - return begin + uint64(g.r.Intn(int(end-begin+1))) +// RandInt returns a non-negative pseudo-random int. +func (br *BuzzerRNG) RandInt() int { + br.mu.Lock() + defer br.mu.Unlock() + return br.r.Int() } -// OneOf returns true 1 out of n times -func (g *NumGen) OneOf(n int) bool { - return g.r.Intn(n) == 0 +// RandRange returns a non-negative pseudo-random number in [min, max). +func (br *BuzzerRNG) RandRange(min, max uint64) uint64 { + br.mu.Lock() + defer br.mu.Unlock() + return uint64(br.r.Intn(int(max-min+1))) + min } -// NOutOf returns true n out of outOf times. -func (g *NumGen) NOutOf(n, outOf int) bool { - if n <= 0 || n >= outOf { - panic("bad probability") +// RandBytes returns a byte slice of the given size populated with pseudo-random +// data. +func (br *BuzzerRNG) RandBytes(size int) []byte { + b := make([]byte, size) + br.mu.Lock() + defer br.mu.Unlock() + if _, err := br.r.Read(b); err != nil { + // This should never fail + panic(err) } - v := g.r.Intn(outOf) - return v < n + return b } -// RandInt is the preferred method for generating a random integer. It is biased towards -// 'special' numbers such as 256, 4096, 1 << 31, 1 << 63 etc. -func (g *NumGen) RandInt() uint64 { - v := uint64(g.r.Int63()) +// RandString returns a string of the given size populated with pseudo-random +// data. +func (br *BuzzerRNG) RandString(size int) string { + return string(br.RandBytes(size)) +} + +// RandBool returns a random boolean. +func (br *BuzzerRNG) RandBool() bool { + br.mu.Lock() + defer br.mu.Unlock() + return br.r.Intn(2) == 1 +} - // All of these proababilities are subject to tuning and can be changed at any time for experiments - switch { - case g.NOutOf(3, 10): - v = specialInts[g.r.Intn(len(specialInts))] - case g.NOutOf(1, 10): - v %= 256 - case g.NOutOf(1, 10): - v %= 64 << 10 - case g.NOutOf(1, 10): - v %= 1 << 31 - case g.NOutOf(1, 10): - v = uint64(-int64(v)) +// OneOf returns a random element from the given slice. +// It panics if the slice is empty. +func OneOf[T any](choices []T) T { + if len(choices) == 0 { + panic("OneOf called with empty slice") } - return v + // Get a random index from the thread-safe SharedRNG + idx := SharedRNG.RandRange(0, uint64(len(choices))) + return choices[idx] } From 99f9d5fe095f3928633a89c220d1b04952225f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20L=C3=B3pez=20Jaimez?= Date: Fri, 24 Oct 2025 15:44:59 +0000 Subject: [PATCH 2/2] run the formatter script --- ebpf_ffi/ffi.cc | 2 +- pkg/cbpf/instruction_generators.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ebpf_ffi/ffi.cc b/ebpf_ffi/ffi.cc index 426c9a9..7277b80 100644 --- a/ebpf_ffi/ffi.cc +++ b/ebpf_ffi/ffi.cc @@ -123,7 +123,7 @@ int ffi_cleanup_coverage() { close(kCoverageData->fd); if (kCoverageData->coverage_buffer != nullptr) - munmap(kCoverageData->coverage_buffer, KCOV_SIZE * sizeof(uint64_t)); + munmap(kCoverageData->coverage_buffer, KCOV_SIZE * sizeof(uint64_t)); free(kCoverageData); kCoverageData = nullptr; return 0; diff --git a/pkg/cbpf/instruction_generators.go b/pkg/cbpf/instruction_generators.go index 04051c5..ddd46b0 100644 --- a/pkg/cbpf/instruction_generators.go +++ b/pkg/cbpf/instruction_generators.go @@ -54,7 +54,7 @@ func RandomJmpInstruction(maxOffset uint64) *pb.Instruction { } offset := int8(rand.SharedRNG.RandRange(1, maxOffset)) - if rand.SharedRNG.RandBool() { + if rand.SharedRNG.RandBool() { src := int32(rand.SharedRNG.RandRange(0, 0xffffffff)) return newJmpInstruction(op, 0, int32(offset), src) } else {