Skip to content

Commit a08fe91

Browse files
committed
initial commit
1 parent cfd2e10 commit a08fe91

32 files changed

+2843
-0
lines changed

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Allow docker files
2+
!Dockerfile
3+
4+
/.cache
5+
6+
.idea/
7+
.vscode/
8+
.clangd
9+
.antlr/
10+
11+
# Develop IDE
12+
.vs/
13+
14+
# fuzzing example
15+
example/corpus/
16+
example/cov*
17+
example/crash*
18+
example/go-fuzz.a
19+
example/go-fuzz.h
20+
example/fuzz
21+
example/result.html
22+
example/result.txt
23+
example/result_func.txt

CONTRIBUTING.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Notice to external contributors
2+
3+
## General info
4+
5+
Hello! In order for us (YANDEX LLC) to accept patches and other contributions from you, you will have to adopt our Contributor License Agreement (the “CLA”). The current version of the CLA you may find here:
6+
7+
1. https://yandex.ru/legal/cla/?lang=en (in English) and
8+
2. https://yandex.ru/legal/cla/?lang=ru (in Russian).
9+
10+
By adopting the CLA, you state the following:
11+
12+
- You obviously wish and are willingly licensing your contributions to us for our open source projects under the terms of the CLA,
13+
- You have read the terms and conditions of the CLA and agree with them in full,
14+
- You are legally able to provide and license your contributions as stated,
15+
- We may use your contributions for our open source projects and for any other our project too,
16+
- We rely on your assurances concerning the rights of third parties in relation to your contributions.
17+
18+
If you agree with these principles, please read and adopt our CLA. By providing us your contributions, you hereby declare that you have read and adopted our CLA, and we may freely merge your contributions with our corresponding open source project and use it in further in accordance with terms and conditions of the CLA.
19+
20+
## Other questions
21+
22+
If you have any questions, please write us at [email protected].

README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
## Mutator
2+
3+
This is a go-protobuf-mutator library for random value mutations. (See also [libprotobuf-mutator](https://github.com/google/libprotobuf-mutator))
4+
5+
### Supported Types
6+
- ProtoMessage
7+
- Bool
8+
- Float64
9+
- Float32
10+
- Uint32
11+
- Uint64
12+
- Int32
13+
- Int64
14+
- String
15+
- Bytes
16+
17+
### Dependencies for libfuzzer
18+
- Go 1.21+
19+
- Protocol Buffers compiler (`protoc`)
20+
- libprotobuf-dev
21+
- clang (for libFuzzer integration)
22+
- libprotobuf-mutator (v1.1)
23+
24+
### Basic Usage
25+
```go
26+
message := NewProtoMessage()
27+
mutator := mutator.New(int64(seed), maxSize)
28+
if err := mutator.MutateProto(message); err != nil {
29+
fmt.Printf("Failed to mutate message: %+v", err)
30+
}
31+
```
32+
33+
### Implementation Examples
34+
35+
For complete reference implementations:
36+
37+
| Feature | Location | Documentation |
38+
|-----------------------|-----------------------------------|-------------------------------------|
39+
| libFuzzer Integration | [`example/cmd/libfuzzer.go`](./example/cmd/libfuzzer.go) | [README](./example/README.md) |
40+
| Coverage Tracking | [`example/cmd/coverage.go`](./example/cmd/coverage.go) | Code comments |
41+
42+
**Pro Tip**: The libFuzzer example includes:
43+
- Custom mutator setup
44+
- Seed corpus generation
45+
- Crash reproduction workflow
46+
- Performance benchmarking
47+
48+
### Integration with libFuzzer
49+
Add `--tag libfuzzer` to build commands when using libFuzzer.
50+
51+
#### Example Code
52+
```go
53+
// #include <stdint.h>
54+
import "C"
55+
56+
//export LLVMFuzzerTestOneInput
57+
func LLVMFuzzerTestOneInput(data *C.char, size C.size_t) C.int {
58+
gdata := unsafe.Slice((*byte)(unsafe.Pointer(data)), size)
59+
message := NewProtoMessage(gdata)
60+
Fuzz(message)
61+
return 0
62+
}
63+
64+
//export LLVMFuzzerCustomMutator
65+
func LLVMFuzzerCustomMutator(data *C.char, size C.size_t, maxSize C.size_t, seed C.uint) C.size_t {
66+
gdata := unsafe.Slice((*byte)(unsafe.Pointer(data)), size)
67+
message := NewProtoMessage(gdata)
68+
69+
mutator := mutator.New(int64(seed), int(maxSize-size))
70+
if err := mutator.MutateProto(message); err != nil {
71+
return 0
72+
}
73+
74+
gdata = unsafe.Slice((*byte)(unsafe.Pointer(data)), maxSize)
75+
newSize := StoreMessage(gdata, message)
76+
return C.size_t(newSize)
77+
}
78+
```
79+
80+
#### Build Commands
81+
```sh
82+
go build -tags libfuzzer -buildmode c-archive -o fuzz.a ./cmd/fuzzing
83+
clang++ -o fuzz fuzz.a -fsanitize=fuzzer
84+
./fuzz ./corpus
85+
```

SECURITY.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Security Policy
2+
3+
## Reporting a Vulnerability
4+
5+
We're extremely grateful for security researchers and users who report vulnerabilities they discovered in mutator. All reports are thoroughly investigated.
6+
7+
To report a potential vulnerability in mutator please email details to [[email protected]](mailto:[email protected]).
8+
9+
### When Should I Report a Vulnerability?
10+
11+
- You think you discovered a potential security vulnerability in mutator
12+
- You are unsure how a vulnerability affects mutator
13+
14+
## Security Vulnerability Response
15+
16+
Each report is acknowledged and analyzed by mutator maintainers within 5 working days.
17+
We will keep the reporter informed about the issue progress.
18+
19+
## Public Disclosure Timing
20+
21+
A public disclosure date is negotiated by mutator maintainers and the bug submitter. We prefer to fully disclose the bug as soon as possible once a mitigation is available for mutator users. It is reasonable to delay disclosure when the bug or the fix is not yet fully understood, the solution is not well-tested, or for vendor coordination. The timeframe for disclosure is from immediate (especially if it's already publicly known) to 90 days. For a vulnerability with a straightforward mitigation, we expect report date to disclosure date to be on the order of 7 days.

base.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package mutator
2+
3+
import (
4+
"encoding/binary"
5+
"math/rand"
6+
"unsafe"
7+
)
8+
9+
var nativeEndian binary.ByteOrder
10+
11+
func init() {
12+
buf := [2]byte{}
13+
*(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD)
14+
15+
switch buf {
16+
case [2]byte{0xCD, 0xAB}:
17+
nativeEndian = binary.LittleEndian
18+
case [2]byte{0xAB, 0xCD}:
19+
nativeEndian = binary.BigEndian
20+
default:
21+
panic("Could not determine native endian.")
22+
}
23+
}
24+
25+
// Return random integer from [0, count)
26+
func getRandomRange(src *rand.Rand, count int) int {
27+
// validate count
28+
// because rand panics if count <= 0
29+
if count <= 0 {
30+
return 0
31+
}
32+
return src.Intn(count)
33+
}
34+
35+
func MutateInt64(src *rand.Rand, value *int64) error {
36+
return mutateValue(src, value)
37+
}
38+
39+
func MutateInt32(src *rand.Rand, value *int32) error {
40+
return mutateValue(src, value)
41+
}
42+
43+
func MutateUint64(src *rand.Rand, value *uint64) error {
44+
return mutateValue(src, value)
45+
}
46+
47+
func MutateUint32(src *rand.Rand, value *uint32) error {
48+
return mutateValue(src, value)
49+
}
50+
51+
func MutateFloat32(src *rand.Rand, value *float32) error {
52+
return mutateValue(src, value)
53+
}
54+
55+
func MutateFloat64(src *rand.Rand, value *float64) error {
56+
return mutateValue(src, value)
57+
}
58+
59+
func MutateBool(src *rand.Rand, value *bool) error {
60+
return mutateValue(src, value)
61+
}
62+
63+
// Return random bool value
64+
func GetRandomBool(src *rand.Rand) bool {
65+
return GetRandomBoolN(src, 2)
66+
}
67+
68+
// Return true with probability about 1-of-n.
69+
func GetRandomBoolN(src *rand.Rand, n int) bool {
70+
val := getRandomRange(src, n)
71+
return val == 0
72+
}
73+
74+
// Flips random bit in the buffer.
75+
func flipBit(src *rand.Rand, size int, data []byte) {
76+
if len(data) == 0 {
77+
return
78+
}
79+
bit := getRandomRange(src, size*8)
80+
81+
data[bit/8] ^= (1 << (bit % 8))
82+
}

common.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//go:build !libfuzzer
2+
3+
package mutator
4+
5+
import (
6+
"bytes"
7+
"encoding/binary"
8+
"math/rand"
9+
"reflect"
10+
)
11+
12+
// flipBitValue flips random bit in the value.
13+
func mutateValue(src *rand.Rand, value any) error {
14+
data := make([]byte, 0, reflect.TypeOf(value).Size())
15+
buf := bytes.NewBuffer(data)
16+
if err := binary.Write(buf, nativeEndian, value); err != nil {
17+
return err
18+
}
19+
flipBit(src, buf.Len(), buf.Bytes())
20+
return binary.Read(buf, nativeEndian, value)
21+
}
22+
23+
func MutateString(src *rand.Rand, str string, maxSize int) (string, error) {
24+
value, err := MutateBytes(src, []byte(str), maxSize)
25+
if err != nil {
26+
return "", err
27+
}
28+
if err := FixUTF8(value, src); err != nil {
29+
return "", err
30+
}
31+
return string(value), nil
32+
}
33+
34+
func MutateBytes(src *rand.Rand, value []byte, maxSize int) ([]byte, error) {
35+
result := make([]byte, len(value))
36+
copy(result, value)
37+
38+
for len(result) > 0 && GetRandomBool(src) {
39+
index := getRandomRange(src, len(result))
40+
result = append(result[:index], result[index+1:]...)
41+
}
42+
43+
for len(result) < maxSize && GetRandomBool(src) {
44+
index := getRandomRange(src, len(result)+1)
45+
result = append(result, byte(getRandomRange(src, 1<<8)))
46+
result[index], result[len(result)-1] = result[len(result)-1], result[index]
47+
}
48+
49+
if !bytes.Equal(result, value) {
50+
return result, nil
51+
}
52+
53+
if len(result) == 0 {
54+
result = append(result, byte(getRandomRange(src, 1<<8)))
55+
return result, nil
56+
}
57+
58+
flipBit(src, len(result), result)
59+
return result, nil
60+
}

common_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//go:build !libfuzzer
2+
3+
package mutator
4+
5+
import (
6+
"math/rand"
7+
"reflect"
8+
"testing"
9+
"time"
10+
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestFlipBitValue(t *testing.T) {
15+
src := rand.New(rand.NewSource(time.Now().Unix()))
16+
17+
var (
18+
valueInt32 = int32(32)
19+
valueInt64 = int64(64)
20+
valueUint32 = uint32(32)
21+
valueUint64 = uint64(64)
22+
valueFloat32 = float32(32)
23+
valueFloat64 = float64(64)
24+
valueBool = true
25+
)
26+
27+
tests := []struct {
28+
name string
29+
value any
30+
}{
31+
{
32+
"int32",
33+
&valueInt32,
34+
},
35+
{
36+
"int64",
37+
&valueInt64,
38+
},
39+
{
40+
"uint32",
41+
&valueUint32,
42+
},
43+
{
44+
"uint64",
45+
&valueUint64,
46+
},
47+
{
48+
"float32",
49+
&valueFloat32,
50+
},
51+
{
52+
"float64",
53+
&valueFloat64,
54+
},
55+
{
56+
"bool",
57+
&valueBool,
58+
},
59+
}
60+
for _, tt := range tests {
61+
t.Run(tt.name, func(t *testing.T) {
62+
for run := 0; run < 10000; run++ {
63+
value := reflect.ValueOf(tt.value)
64+
require.NoError(t, mutateValue(src, tt.value))
65+
require.NotEqualValues(t, value, tt.value)
66+
}
67+
})
68+
}
69+
}

0 commit comments

Comments
 (0)