From 8af6e1c1051a4c40b4f31cc83df340219f3dbe95 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 15 Aug 2024 13:21:04 -0500 Subject: [PATCH 1/5] feat: add expansion call sequence mutation --- fuzzing/fuzzer_worker_sequence_generator.go | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/fuzzing/fuzzer_worker_sequence_generator.go b/fuzzing/fuzzer_worker_sequence_generator.go index 666efb23..9fd854b6 100644 --- a/fuzzing/fuzzer_worker_sequence_generator.go +++ b/fuzzing/fuzzer_worker_sequence_generator.go @@ -137,6 +137,13 @@ func NewCallSequenceGenerator(worker *FuzzerWorker, config *CallSequenceGenerato }, new(big.Int).SetUint64(config.RandomUnmodifiedCorpusTailWeight), ), + randomutils.NewWeightedRandomChoice( + CallSequenceGeneratorMutationStrategy{ + CallSequenceGeneratorFunc: callSeqGenFuncExpansion, + PrefetchModifyCallFunc: nil, + }, + new(big.Int).SetUint64(config.RandomUnmodifiedCorpusTailWeight), + ), randomutils.NewWeightedRandomChoice( CallSequenceGeneratorMutationStrategy{ CallSequenceGeneratorFunc: callSeqGenFuncSpliceAtRandom, @@ -380,6 +387,29 @@ func callSeqGenFuncCorpusTail(sequenceGenerator *CallSequenceGenerator, sequence return nil } +// callSeqGenFuncExpansion is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a +// sequence which is expanded up to 30 times by replicating an existing call sequence element at a random position. +func callSeqGenFuncExpansion(sequenceGenerator *CallSequenceGenerator, sequence calls.CallSequence) error { + rounds := sequenceGenerator.worker.randomProvider.Intn(31) + + // Get item to expand + randIndex := sequenceGenerator.worker.randomProvider.Intn(len(sequence)) + duplicatedElement := sequence[randIndex] + + // Perform N rounds of expansion + for i := 0; i < rounds; i++ { + randIndex += i + if randIndex < len(sequence) { + // Insert + sequence = append(sequence[:randIndex], append([]*calls.CallSequenceElement{duplicatedElement}, sequence[randIndex:]...)...) + } else { + // Extend + sequence = append(sequence, duplicatedElement) + } + } + return nil +} + // callSeqGenFuncSpliceAtRandom is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a // sequence which is based off of two corpus call sequence entries, from which a random length head and tail are // respectively sliced and joined together. From 8b023745fc043691c1bffe77c22dc5743511f85a Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 26 Aug 2024 11:04:42 -0500 Subject: [PATCH 2/5] wip --- fuzzing/fuzzer_worker_sequence_generator.go | 210 ++++++++++++++------ 1 file changed, 149 insertions(+), 61 deletions(-) diff --git a/fuzzing/fuzzer_worker_sequence_generator.go b/fuzzing/fuzzer_worker_sequence_generator.go index 9fd854b6..54d2fb42 100644 --- a/fuzzing/fuzzer_worker_sequence_generator.go +++ b/fuzzing/fuzzer_worker_sequence_generator.go @@ -3,11 +3,11 @@ package fuzzing import ( "fmt" "math/big" + "math/rand" "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/valuegeneration" - "github.com/crytic/medusa/utils" "github.com/crytic/medusa/utils/randomutils" ) @@ -186,6 +186,18 @@ func NewCallSequenceGenerator(worker *FuzzerWorker, config *CallSequenceGenerato }, new(big.Int).SetUint64(config.RandomMutatedInterleaveAtRandomWeight), ), + randomutils.NewWeightedRandomChoice(CallSequenceGeneratorMutationStrategy{ + CallSequenceGeneratorFunc: callSeqSwapRandomElement, + PrefetchModifyCallFunc: nil, + }, + new(big.Int).SetUint64(config.RandomMutatedInterleaveAtRandomWeight), + ), + randomutils.NewWeightedRandomChoice(CallSequenceGeneratorMutationStrategy{ + CallSequenceGeneratorFunc: callSeqDeleteRandomElement, + PrefetchModifyCallFunc: nil, + }, + new(big.Int).SetUint64(config.RandomMutatedInterleaveAtRandomWeight), + ), ) return generator @@ -352,6 +364,24 @@ func (g *CallSequenceGenerator) generateNewElement() (*calls.CallSequenceElement return calls.NewCallSequenceElement(selectedMethod.Contract, msg, blockNumberDelay, blockTimestampDelay), nil } +func callSeqSwapRandomElement(sequenceGenerator *CallSequenceGenerator, sequence calls.CallSequence) error { + // Swap the element + swappedSequence := swapRandList(sequence) + + copy(sequence, swappedSequence) + + return nil +} + +func callSeqDeleteRandomElement(sequenceGenerator *CallSequenceGenerator, sequence calls.CallSequence) error { + // Delete the element + deletedSequence := deleteRandList(sequence) + + copy(sequence, deletedSequence) + + return nil +} + // callSeqGenFuncCorpusHead is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a sequence // whose head is based off of an existing corpus call sequence. // Returns an error if one occurs. @@ -362,9 +392,10 @@ func callSeqGenFuncCorpusHead(sequenceGenerator *CallSequenceGenerator, sequence return fmt.Errorf("could not obtain corpus call sequence for head mutation: %v", err) } - // Determine the length of the slice to be copied in the head. - maxLength := utils.Min(len(sequence), len(corpusSequence)) - copy(sequence, corpusSequence[:maxLength]) + // Append the new calls to the end of the corpus sequence + spliced := append(corpusSequence, sequence...) + + copy(sequence, spliced) return nil } @@ -379,10 +410,10 @@ func callSeqGenFuncCorpusTail(sequenceGenerator *CallSequenceGenerator, sequence return fmt.Errorf("could not obtain corpus call sequence for tail mutation: %v", err) } - // Determine a random position to slice the call sequence. - maxLength := utils.Min(len(sequence), len(corpusSequence)) - targetLength := sequenceGenerator.worker.randomProvider.Intn(maxLength) + 1 - copy(sequence[len(sequence)-targetLength:], corpusSequence[len(corpusSequence)-targetLength:]) + // Prepend the new calls to the start of the corpus sequence + spliced := append(sequence, corpusSequence...) + + copy(sequence, spliced) return nil } @@ -390,23 +421,12 @@ func callSeqGenFuncCorpusTail(sequenceGenerator *CallSequenceGenerator, sequence // callSeqGenFuncExpansion is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a // sequence which is expanded up to 30 times by replicating an existing call sequence element at a random position. func callSeqGenFuncExpansion(sequenceGenerator *CallSequenceGenerator, sequence calls.CallSequence) error { - rounds := sequenceGenerator.worker.randomProvider.Intn(31) - - // Get item to expand - randIndex := sequenceGenerator.worker.randomProvider.Intn(len(sequence)) - duplicatedElement := sequence[randIndex] - - // Perform N rounds of expansion - for i := 0; i < rounds; i++ { - randIndex += i - if randIndex < len(sequence) { - // Insert - sequence = append(sequence[:randIndex], append([]*calls.CallSequenceElement{duplicatedElement}, sequence[randIndex:]...)...) - } else { - // Extend - sequence = append(sequence, duplicatedElement) - } - } + + // Expand the sequence + expandedSequence := expandRandList(sequence) + + copy(sequence, expandedSequence) + return nil } @@ -425,19 +445,10 @@ func callSeqGenFuncSpliceAtRandom(sequenceGenerator *CallSequenceGenerator, sequ return fmt.Errorf("could not obtain tail corpus call sequence for splice-at-random corpus mutation: %v", err) } - // Determine a random position to slice off the head of the call sequence. - maxLength := utils.Min(len(sequence), len(headSequence)) - headSequenceLength := sequenceGenerator.worker.randomProvider.Intn(maxLength) + 1 - - // Copy the head of the first corpus sequence to our destination sequence. - copy(sequence, headSequence[:headSequenceLength]) - - // Determine a random position to slice off the tail of the call sequence. - maxLength = utils.Min(len(sequence)-headSequenceLength, len(tailSequence)) - tailSequenceLength := sequenceGenerator.worker.randomProvider.Intn(maxLength + 1) + // Splice the two sequences + splicedSequence := spliceAtRandom(headSequence, tailSequence) - // Copy the tail of the second corpus sequence to our destination sequence (after the head sequence portion). - copy(sequence[headSequenceLength:], tailSequence[len(tailSequence)-tailSequenceLength:]) + copy(sequence, splicedSequence) return nil } @@ -457,30 +468,11 @@ func callSeqGenFuncInterleaveAtRandom(sequenceGenerator *CallSequenceGenerator, return fmt.Errorf("could not obtain second corpus call sequence for interleave-at-random corpus mutation: %v", err) } - // Determine how many transactions to take from the first sequence and slice it. - maxLength := utils.Min(len(sequence), len(firstSequence)) - firstSequenceLength := sequenceGenerator.worker.randomProvider.Intn(maxLength) + 1 - firstSequence = firstSequence[:firstSequenceLength] - - // Determine how many transactions to take from the second sequence and slice it. - maxLength = utils.Min(len(sequence)-firstSequenceLength, len(secondSequence)) - secondSequenceLength := sequenceGenerator.worker.randomProvider.Intn(maxLength + 1) - secondSequence = secondSequence[:secondSequenceLength] - - // Now that we have both sequences, and we know they will not exceed our destination sequence length, interleave - // them. - destIndex := 0 - largestSequenceSize := utils.Max(firstSequenceLength, secondSequenceLength) - for i := 0; i < largestSequenceSize; i++ { - if i < len(firstSequence) { - sequence[destIndex] = firstSequence[i] - destIndex++ - } - if i < len(secondSequence) { - sequence[destIndex] = secondSequence[i] - destIndex++ - } - } + // Interleave the two sequences + interleavedSequence := interleaveAtRandom(firstSequence, secondSequence) + + copy(sequence, interleavedSequence) + return nil } @@ -507,3 +499,99 @@ func prefetchModifyCallFuncMutate(sequenceGenerator *CallSequenceGenerator, elem return nil } + +// expandAt expands the element at index k by t times. +func expandAt[T any](xs []T, k int, t int) []T { + if len(xs) == 0 { + return xs + } + return append(append(xs[:k], repeat(xs[k], t)...), xs[k+1:]...) +} + +// repeat replicates an element t times. +func repeat[T any](v T, t int) []T { + res := make([]T, t) + for i := range res { + res[i] = v + } + return res +} + +// expandRandList expands a random element of the list by 1 to 32 times. +func expandRandList[T any](xs []T) []T { + l := len(xs) + if l == 0 || l >= 32 { + return xs + } + k := rand.Intn(l) + t := rand.Intn(min(32, l)) + 1 + return expandAt(xs, k, t) +} + +// deleteAt deletes the element at the given index. +func deleteAt[T any](xs []T, n int) []T { + return append(xs[:n], xs[n+1:]...) +} + +// deleteRandList deletes a random element from the list. +func deleteRandList[T any](xs []T) []T { + if len(xs) == 0 { + return xs + } + k := rand.Intn(len(xs)) + return deleteAt(xs, k) +} + +// swapAt swaps two elements in the list. +func swapAt[T any](xs []T, i, j int) []T { + xs[i], xs[j] = xs[j], xs[i] + return xs +} + +// swapRandList swaps two random elements in the list. +func swapRandList[T any](xs []T) []T { + if len(xs) == 0 { + return xs + } + i, j := rand.Intn(len(xs)), rand.Intn(len(xs)) + return swapAt(xs, min(i, j), max(i, j)) +} + +// spliceAtRandom splices two lists at random positions. +func spliceAtRandom[T any](xs1, xs2 []T) []T { + idx1, idx2 := rand.Intn(len(xs1)), rand.Intn(len(xs2)) + return append(xs1[:idx1], xs2[idx2:]...) +} + +// interleaveAtRandom interleaves two lists at random positions. +func interleaveAtRandom[T any](xs1, xs2 []T) []T { + idx1, idx2 := rand.Intn(len(xs1)), rand.Intn(len(xs2)) + return interleaveLL(xs1[:idx1], xs2[:idx2]) +} + +// interleaveLL interleaves two lists. +func interleaveLL[T any](a, b []T) []T { + if len(a) == 0 { + return b + } + if len(b) == 0 { + return a + } + return append([]T{a[0], b[0]}, interleaveLL(a[1:], b[1:])...) +} + +// min returns the smaller of two integers. +func min(a, b int) int { + if a < b { + return a + } + return b +} + +// max returns the larger of two integers. +func max(a, b int) int { + if a > b { + return a + } + return b +} From 7260ca1f07b8839e5db2826844fb5fe462e43d23 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 5 Sep 2024 14:31:23 -0500 Subject: [PATCH 3/5] wip --- ...nce_generator.go => sequence_generator.go} | 135 +++++++++--------- fuzzing/sequence_generator_test.go | 122 ++++++++++++++++ 2 files changed, 192 insertions(+), 65 deletions(-) rename fuzzing/{fuzzer_worker_sequence_generator.go => sequence_generator.go} (88%) create mode 100644 fuzzing/sequence_generator_test.go diff --git a/fuzzing/fuzzer_worker_sequence_generator.go b/fuzzing/sequence_generator.go similarity index 88% rename from fuzzing/fuzzer_worker_sequence_generator.go rename to fuzzing/sequence_generator.go index 54d2fb42..99114d54 100644 --- a/fuzzing/fuzzer_worker_sequence_generator.go +++ b/fuzzing/sequence_generator.go @@ -8,6 +8,7 @@ import ( "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/valuegeneration" + "github.com/crytic/medusa/utils" "github.com/crytic/medusa/utils/randomutils" ) @@ -98,7 +99,7 @@ type CallSequenceGeneratorConfig struct { // CallSequenceGeneratorFunc defines a method used to populate a provided call sequence with generated calls. // Returns an optional PrefetchModifyCallFunc to be executed prior to the fetching of each element, or an error if // one occurs. -type CallSequenceGeneratorFunc func(sequenceGenerator *CallSequenceGenerator, sequence calls.CallSequence) error +type CallSequenceGeneratorFunc func(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error // PrefetchModifyCallFunc defines a method used to modify a call sequence element before being fetched from this // provider for use. @@ -241,7 +242,7 @@ func (g *CallSequenceGenerator) InitializeNextSequence() (bool, error) { // If we have a corpus mutation method, call it to generate our base sequence, then set the pre-fetch modify // call function. if corpusMutationFunc != nil && corpusMutationFunc.CallSequenceGeneratorFunc != nil { - err = corpusMutationFunc.CallSequenceGeneratorFunc(g, g.baseSequence) + err = corpusMutationFunc.CallSequenceGeneratorFunc(g.worker.randomProvider, g.worker.fuzzer.corpus.RandomMutationTargetSequence, g.baseSequence) if err != nil { return true, fmt.Errorf("could not generate a corpus mutation derived call sequence due to an error executing a mutation method: %v", err) } @@ -364,18 +365,42 @@ func (g *CallSequenceGenerator) generateNewElement() (*calls.CallSequenceElement return calls.NewCallSequenceElement(selectedMethod.Contract, msg, blockNumberDelay, blockTimestampDelay), nil } -func callSeqSwapRandomElement(sequenceGenerator *CallSequenceGenerator, sequence calls.CallSequence) error { +// prefetchModifyCallFuncMutate is a PrefetchModifyCallFunc, called by a CallSequenceGenerator to apply mutations +// to a call sequence element, prior to it being fetched. +// Returns an error if one occurs. +func prefetchModifyCallFuncMutate(sequenceGenerator *CallSequenceGenerator, element *calls.CallSequenceElement) error { + // If this element has no ABI value based call data, exit early. + if element.Call == nil || element.Call.DataAbiValues == nil { + return nil + } + + // Loop for each input value and mutate it + abiValuesMsgData := element.Call.DataAbiValues + for i := 0; i < len(abiValuesMsgData.InputValues); i++ { + mutatedInput, err := valuegeneration.MutateAbiValue(sequenceGenerator.config.ValueGenerator, sequenceGenerator.config.ValueMutator, &abiValuesMsgData.Method.Inputs[i].Type, abiValuesMsgData.InputValues[i]) + if err != nil { + return fmt.Errorf("error when mutating call sequence input argument: %v", err) + } + abiValuesMsgData.InputValues[i] = mutatedInput + } + // Re-encode the message's calldata + element.Call.WithDataAbiValues(abiValuesMsgData) + + return nil +} + +func callSeqSwapRandomElement(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { // Swap the element - swappedSequence := swapRandList(sequence) + swappedSequence := swapRandList(provider, sequence) copy(sequence, swappedSequence) return nil } -func callSeqDeleteRandomElement(sequenceGenerator *CallSequenceGenerator, sequence calls.CallSequence) error { +func callSeqDeleteRandomElement(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { // Delete the element - deletedSequence := deleteRandList(sequence) + deletedSequence := deleteRandList(provider, sequence) copy(sequence, deletedSequence) @@ -385,15 +410,16 @@ func callSeqDeleteRandomElement(sequenceGenerator *CallSequenceGenerator, sequen // callSeqGenFuncCorpusHead is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a sequence // whose head is based off of an existing corpus call sequence. // Returns an error if one occurs. -func callSeqGenFuncCorpusHead(sequenceGenerator *CallSequenceGenerator, sequence calls.CallSequence) error { +func callSeqGenFuncCorpusHead(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { // Obtain a call sequence from the corpus - corpusSequence, err := sequenceGenerator.worker.fuzzer.corpus.RandomMutationTargetSequence() + corpusSequence, err := sequenceGenerator() if err != nil { return fmt.Errorf("could not obtain corpus call sequence for head mutation: %v", err) } - // Append the new calls to the end of the corpus sequence - spliced := append(corpusSequence, sequence...) + // Prepend the new calls to the end of the corpus sequence + i := provider.Intn(len(corpusSequence)) + 1 + spliced := append(corpusSequence[:i], sequence...) copy(sequence, spliced) @@ -403,15 +429,18 @@ func callSeqGenFuncCorpusHead(sequenceGenerator *CallSequenceGenerator, sequence // callSeqGenFuncCorpusTail is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a sequence // whose tail is based off of an existing corpus call sequence. // Returns an error if one occurs. -func callSeqGenFuncCorpusTail(sequenceGenerator *CallSequenceGenerator, sequence calls.CallSequence) error { +func callSeqGenFuncCorpusTail(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { // Obtain a call sequence from the corpus - corpusSequence, err := sequenceGenerator.worker.fuzzer.corpus.RandomMutationTargetSequence() + corpusSequence, err := sequenceGenerator() if err != nil { return fmt.Errorf("could not obtain corpus call sequence for tail mutation: %v", err) } - // Prepend the new calls to the start of the corpus sequence - spliced := append(sequence, corpusSequence...) + maxLength := utils.Min(len(sequence), len(corpusSequence)) + i := provider.Intn(maxLength) + 1 + fmt.Println("i: ", i) + // Append the new calls to the end of the corpus sequence + spliced := append(sequence[i:], corpusSequence...) copy(sequence, spliced) @@ -420,10 +449,10 @@ func callSeqGenFuncCorpusTail(sequenceGenerator *CallSequenceGenerator, sequence // callSeqGenFuncExpansion is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a // sequence which is expanded up to 30 times by replicating an existing call sequence element at a random position. -func callSeqGenFuncExpansion(sequenceGenerator *CallSequenceGenerator, sequence calls.CallSequence) error { +func callSeqGenFuncExpansion(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { // Expand the sequence - expandedSequence := expandRandList(sequence) + expandedSequence := expandRandList(provider, sequence) copy(sequence, expandedSequence) @@ -434,19 +463,18 @@ func callSeqGenFuncExpansion(sequenceGenerator *CallSequenceGenerator, sequence // sequence which is based off of two corpus call sequence entries, from which a random length head and tail are // respectively sliced and joined together. // Returns an error if one occurs. -func callSeqGenFuncSpliceAtRandom(sequenceGenerator *CallSequenceGenerator, sequence calls.CallSequence) error { +func callSeqGenFuncSpliceAtRandom(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { // Obtain two corpus call sequence entries - headSequence, err := sequenceGenerator.worker.fuzzer.corpus.RandomMutationTargetSequence() + headSequence, err := sequenceGenerator() if err != nil { - return fmt.Errorf("could not obtain head corpus call sequence for splice-at-random corpus mutation: %v", err) } - tailSequence, err := sequenceGenerator.worker.fuzzer.corpus.RandomMutationTargetSequence() + tailSequence, err := sequenceGenerator() if err != nil { return fmt.Errorf("could not obtain tail corpus call sequence for splice-at-random corpus mutation: %v", err) } // Splice the two sequences - splicedSequence := spliceAtRandom(headSequence, tailSequence) + splicedSequence := spliceAtRandom(provider, headSequence, tailSequence) copy(sequence, splicedSequence) @@ -457,51 +485,27 @@ func callSeqGenFuncSpliceAtRandom(sequenceGenerator *CallSequenceGenerator, sequ // sequence which is based off of two corpus call sequence entries, from which a random number of transactions are // taken and interleaved (each element of one sequence will be followed by an element of the other). // Returns an error if one occurs. -func callSeqGenFuncInterleaveAtRandom(sequenceGenerator *CallSequenceGenerator, sequence calls.CallSequence) error { +func callSeqGenFuncInterleaveAtRandom(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { // Obtain two corpus call sequence entries - firstSequence, err := sequenceGenerator.worker.fuzzer.corpus.RandomMutationTargetSequence() + firstSequence, err := sequenceGenerator() if err != nil { return fmt.Errorf("could not obtain first corpus call sequence for interleave-at-random corpus mutation: %v", err) } - secondSequence, err := sequenceGenerator.worker.fuzzer.corpus.RandomMutationTargetSequence() + secondSequence, err := sequenceGenerator() if err != nil { return fmt.Errorf("could not obtain second corpus call sequence for interleave-at-random corpus mutation: %v", err) } // Interleave the two sequences - interleavedSequence := interleaveAtRandom(firstSequence, secondSequence) + interleavedSequence := interleaveAtRandom(provider, firstSequence, secondSequence) copy(sequence, interleavedSequence) return nil } -// prefetchModifyCallFuncMutate is a PrefetchModifyCallFunc, called by a CallSequenceGenerator to apply mutations -// to a call sequence element, prior to it being fetched. -// Returns an error if one occurs. -func prefetchModifyCallFuncMutate(sequenceGenerator *CallSequenceGenerator, element *calls.CallSequenceElement) error { - // If this element has no ABI value based call data, exit early. - if element.Call == nil || element.Call.DataAbiValues == nil { - return nil - } - - // Loop for each input value and mutate it - abiValuesMsgData := element.Call.DataAbiValues - for i := 0; i < len(abiValuesMsgData.InputValues); i++ { - mutatedInput, err := valuegeneration.MutateAbiValue(sequenceGenerator.config.ValueGenerator, sequenceGenerator.config.ValueMutator, &abiValuesMsgData.Method.Inputs[i].Type, abiValuesMsgData.InputValues[i]) - if err != nil { - return fmt.Errorf("error when mutating call sequence input argument: %v", err) - } - abiValuesMsgData.InputValues[i] = mutatedInput - } - // Re-encode the message's calldata - element.Call.WithDataAbiValues(abiValuesMsgData) - - return nil -} - // expandAt expands the element at index k by t times. -func expandAt[T any](xs []T, k int, t int) []T { +func expandAt[T any](xs []*T, k int, t int) []*T { if len(xs) == 0 { return xs } @@ -509,8 +513,8 @@ func expandAt[T any](xs []T, k int, t int) []T { } // repeat replicates an element t times. -func repeat[T any](v T, t int) []T { - res := make([]T, t) +func repeat[T any](v *T, t int) []*T { + res := make([]*T, t) for i := range res { res[i] = v } @@ -518,38 +522,39 @@ func repeat[T any](v T, t int) []T { } // expandRandList expands a random element of the list by 1 to 32 times. -func expandRandList[T any](xs []T) []T { +func expandRandList[T any](provider *rand.Rand, xs []*T) []*T { l := len(xs) if l == 0 || l >= 32 { return xs } - k := rand.Intn(l) - t := rand.Intn(min(32, l)) + 1 + k := provider.Intn(l) + t := provider.Intn(min(32, l)) + 1 return expandAt(xs, k, t) } // deleteAt deletes the element at the given index. -func deleteAt[T any](xs []T, n int) []T { - return append(xs[:n], xs[n+1:]...) +func deleteAt[T any](xs []*T, n int) []*T { + xs[n] = nil + return xs } // deleteRandList deletes a random element from the list. -func deleteRandList[T any](xs []T) []T { +func deleteRandList[T any](provider *rand.Rand, xs []*T) []*T { if len(xs) == 0 { return xs } - k := rand.Intn(len(xs)) + k := provider.Intn(len(xs)) return deleteAt(xs, k) } // swapAt swaps two elements in the list. -func swapAt[T any](xs []T, i, j int) []T { +func swapAt[T any](xs []*T, i, j int) []*T { xs[i], xs[j] = xs[j], xs[i] return xs } // swapRandList swaps two random elements in the list. -func swapRandList[T any](xs []T) []T { +func swapRandList[T any](provider *rand.Rand, xs []*T) []*T { if len(xs) == 0 { return xs } @@ -558,26 +563,26 @@ func swapRandList[T any](xs []T) []T { } // spliceAtRandom splices two lists at random positions. -func spliceAtRandom[T any](xs1, xs2 []T) []T { +func spliceAtRandom[T any](provider *rand.Rand, xs1, xs2 []*T) []*T { idx1, idx2 := rand.Intn(len(xs1)), rand.Intn(len(xs2)) return append(xs1[:idx1], xs2[idx2:]...) } // interleaveAtRandom interleaves two lists at random positions. -func interleaveAtRandom[T any](xs1, xs2 []T) []T { +func interleaveAtRandom[T any](provider *rand.Rand, xs1, xs2 []*T) []*T { idx1, idx2 := rand.Intn(len(xs1)), rand.Intn(len(xs2)) return interleaveLL(xs1[:idx1], xs2[:idx2]) } // interleaveLL interleaves two lists. -func interleaveLL[T any](a, b []T) []T { +func interleaveLL[T any](a, b []*T) []*T { if len(a) == 0 { return b } if len(b) == 0 { return a } - return append([]T{a[0], b[0]}, interleaveLL(a[1:], b[1:])...) + return append([]*T{a[0], b[0]}, interleaveLL(a[1:], b[1:])...) } // min returns the smaller of two integers. diff --git a/fuzzing/sequence_generator_test.go b/fuzzing/sequence_generator_test.go new file mode 100644 index 00000000..2466a941 --- /dev/null +++ b/fuzzing/sequence_generator_test.go @@ -0,0 +1,122 @@ +package fuzzing + +import ( + "fmt" + "math/big" + "math/rand" + + "testing" + + "github.com/crytic/medusa/fuzzing/calls" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +// getMockSimpleCorpusEntry creates a mock CorpusCallSequence with numBlocks blocks for testing +func getMockCallSequence(size, data int) calls.CallSequence { + cs := make(calls.CallSequence, size) + for i := 0; i < size; i++ { + cs[i] = getMockCallSequenceElement(data) + } + return cs +} + +// getMockSimpleBlockBlock creates a mock CorpusBlock with numTransactions transactions and receipts for testing +func getMockCallSequenceElement(data int) *calls.CallSequenceElement { + + return &calls.CallSequenceElement{ + Contract: nil, + Call: getMockCallSequenceElementCall(data), + BlockNumberDelay: rand.Uint64(), + BlockTimestampDelay: rand.Uint64(), + ChainReference: nil, + } +} + +// getMockCallSequenceElementCall creates a mock CallMessage for testing +func getMockCallSequenceElementCall(data int) *calls.CallMessage { + to := common.BigToAddress(big.NewInt(rand.Int63())) + txn := calls.CallMessage{ + From: common.BigToAddress(big.NewInt(rand.Int63())), + To: &to, + Nonce: rand.Uint64(), + Value: big.NewInt(int64(rand.Int())), + GasLimit: rand.Uint64(), + GasPrice: big.NewInt(int64(rand.Int())), + GasFeeCap: big.NewInt(int64(rand.Int())), + GasTipCap: big.NewInt(int64(rand.Int())), + Data: []byte{uint8(data), uint8(data), uint8(data), uint8(data)}, + } + return &txn +} + +func TestSplice(t *testing.T) { + strategies := map[string]func(rand *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error{ + // "interleave": callSeqGenFuncInterleaveAtRandom, + // "splice": callSeqGenFuncSpliceAtRandom, + // "expansion": callSeqGenFuncExpansion, + "prepend": callSeqGenFuncCorpusHead, + "apppend": callSeqGenFuncCorpusTail, + // "delete": callSeqDeleteRandomElement, + // "swap": callSeqSwapRandomElement, + } + // Seed the PRNG to make the randomness deterministic + + // Prepare a destination sequence (with the expected size) + // expectedSize := 8 // Adjust based on the expected interleave size in your scenario + + // Prepare a source sequence (with the expected size) + + for name, strategyFn := range strategies { + // Call the function under test + sourceSequence := []calls.CallSequence{getMockCallSequence(1, 1), getMockCallSequence(1, 5)} + i := 0 + sequence := getMockCallSequence(4, 7) + sequence = append(sequence, getMockCallSequence(4, 9)...) + mockSequenceGenerator := func() (calls.CallSequence, error) { + + // Return the source sequence + s := sourceSequence[i] + i++ + return s, nil + } + fmt.Println("Before:") + for _, s := range sequence { + if s == nil { + fmt.Println("nil") + continue + } + fmt.Println(s.Call.Data) + } + // fmt.Println("Source sequence 1:") + // fmt.Println(sourceSequence[0][0].Call.Data) + // fmt.Println(sourceSequence[0][1].Call.Data) + // fmt.Println(sourceSequence[0][2].Call.Data) + // fmt.Println(sourceSequence[0][3].Call.Data) + // fmt.Println("Source sequence 2:") + // fmt.Println(sourceSequence[1][0].Call.Data) + // fmt.Println(sourceSequence[1][1].Call.Data) + // fmt.Println(sourceSequence[1][2].Call.Data) + // fmt.Println(sourceSequence[1][3].Call.Data) + + err := strategyFn(rand.New(rand.NewSource(0)), mockSequenceGenerator, sequence) + + // Ensure no error + assert.NoError(t, err) + + // Check if the interleaved sequence has the correct size and contents + assert.NotNil(t, sequence) + // assert.Len(t, sequence, expectedSize) + + // Check that the elements in the sequence are correctly interleaved + // You can further check for specific fields if necessary + fmt.Printf("Interleaved sequence using %s:\n", name) + for _, s := range sequence { + if s == nil { + fmt.Println("nil") + continue + } + fmt.Println(s.Call.Data) + } + } +} From f1fa669cb6490155694b4b3f13ff461aea06290a Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Fri, 6 Sep 2024 12:58:23 -0500 Subject: [PATCH 4/5] apply mutations to corpus before sequence generation --- fuzzing/fuzzer.go | 20 ++- fuzzing/sequence_generator.go | 242 +++++++++++------------------ fuzzing/sequence_generator_test.go | 14 +- 3 files changed, 109 insertions(+), 167 deletions(-) diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 0b9b1500..b7313bb1 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -569,17 +569,15 @@ func defaultCallSequenceGeneratorConfigFunc(fuzzer *Fuzzer, valueSet *valuegener // Create a sequence generator config which uses the created value generator. sequenceGenConfig := &CallSequenceGeneratorConfig{ - NewSequenceProbability: 0.3, - RandomUnmodifiedCorpusHeadWeight: 800, - RandomUnmodifiedCorpusTailWeight: 100, - RandomUnmodifiedSpliceAtRandomWeight: 200, - RandomUnmodifiedInterleaveAtRandomWeight: 100, - RandomMutatedCorpusHeadWeight: 80, - RandomMutatedCorpusTailWeight: 10, - RandomMutatedSpliceAtRandomWeight: 20, - RandomMutatedInterleaveAtRandomWeight: 10, - ValueGenerator: mutationalGenerator, - ValueMutator: mutationalGenerator, + NewSequenceProbability: 0.3, + Prepend: 800, + Append: 200, + Splice: 100, + Interleave: 100, + PrependAndMutate: 100, + AppendAndMutate: 100, + ValueGenerator: mutationalGenerator, + ValueMutator: mutationalGenerator, } return sequenceGenConfig, nil } diff --git a/fuzzing/sequence_generator.go b/fuzzing/sequence_generator.go index 9d8b2356..14b2ee6f 100644 --- a/fuzzing/sequence_generator.go +++ b/fuzzing/sequence_generator.go @@ -31,10 +31,6 @@ type CallSequenceGenerator struct { // returned when calling PopSequenceElement. fetchIndex int - // prefetchModifyCallFunc describes the method to use to mutate the next indexed call sequence element, prior - // to its fetching by PopSequenceElement. - prefetchModifyCallFunc PrefetchModifyCallFunc - // mutationStrategyChooser is a weighted random selector of functions that prepare the CallSequenceGenerator with // a baseSequence derived from corpus entries. mutationStrategyChooser *randomutils.WeightedRandomChooser[CallSequenceGeneratorMutationStrategy] @@ -47,45 +43,45 @@ type CallSequenceGeneratorConfig struct { // sequence rather than mutating one from the corpus. NewSequenceProbability float32 - // RandomUnmodifiedCorpusHeadWeight defines the weight that the CallSequenceGenerator should use the call sequence + // Prepend defines the weight that the CallSequenceGenerator should use the call sequence // generation strategy of taking the head of a corpus sequence (without mutations) and append newly generated calls // to the end of it. - RandomUnmodifiedCorpusHeadWeight uint64 + Prepend uint64 - // RandomUnmodifiedCorpusTailWeight defines the weight that the CallSequenceGenerator should use the call sequence + // Append defines the weight that the CallSequenceGenerator should use the call sequence // generation strategy of taking the tail of a corpus sequence (without mutations) and prepend newly generated calls // to the start of it. - RandomUnmodifiedCorpusTailWeight uint64 + Append uint64 - // RandomUnmodifiedSpliceAtRandomWeight defines the weight that the CallSequenceGenerator should use the call sequence + // Splice defines the weight that the CallSequenceGenerator should use the call sequence // generation strategy of taking two corpus sequences (without mutations) and splicing them before joining them // together. - RandomUnmodifiedSpliceAtRandomWeight uint64 + Splice uint64 - // RandomUnmodifiedInterleaveAtRandomWeight defines the weight that the CallSequenceGenerator should use the call + // Interleave defines the weight that the CallSequenceGenerator should use the call // sequence generation strategy of taking two corpus sequences (without mutations) and interleaving a random // number of calls from each. - RandomUnmodifiedInterleaveAtRandomWeight uint64 + Interleave uint64 - // RandomMutatedCorpusHeadWeight defines the weight that the CallSequenceGenerator should use the call sequence + // PrependAndMutate defines the weight that the CallSequenceGenerator should use the call sequence // generation strategy of taking the head of a corpus sequence (with mutations) and append newly generated calls // to the end of it. - RandomMutatedCorpusHeadWeight uint64 + PrependAndMutate uint64 - // RandomMutatedCorpusTailWeight defines the weight that the CallSequenceGenerator should use the call sequence + // AppendAndMutate defines the weight that the CallSequenceGenerator should use the call sequence // generation strategy of taking the tao; of a corpus sequence (with mutations) and prepend newly generated calls // to the start of it. - RandomMutatedCorpusTailWeight uint64 + AppendAndMutate uint64 - // RandomMutatedSpliceAtRandomWeight defines the weight that the CallSequenceGenerator should use the call sequence + // SpliceAndMutate defines the weight that the CallSequenceGenerator should use the call sequence // generation strategy of taking two corpus sequences (with mutations) and splicing them before joining them // together. - RandomMutatedSpliceAtRandomWeight uint64 + SpliceAndMutate uint64 - // RandomMutatedInterleaveAtRandomWeight defines the weight that the CallSequenceGenerator should use the call + // InterleaveAndMutate defines the weight that the CallSequenceGenerator should use the call // sequence generation strategy of taking two corpus sequences (with mutations) and interleaving a random // number of calls from each. - RandomMutatedInterleaveAtRandomWeight uint64 + InterleaveAndMutate uint64 // ValueGenerator defines the value provider to use when generating new values for call sequences. This is used both // for ABI call data generation, and generation of additional values such as the "value" field of a @@ -101,18 +97,12 @@ type CallSequenceGeneratorConfig struct { // one occurs. type CallSequenceGeneratorFunc func(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error -// PrefetchModifyCallFunc defines a method used to modify a call sequence element before being fetched from this -// provider for use. -// Returns an error if one occurs. -type PrefetchModifyCallFunc func(sequenceGenerator *CallSequenceGenerator, element *calls.CallSequenceElement) error - // CallSequenceGeneratorMutationStrategy defines a structure for a mutation strategy used by a CallSequenceGenerator. type CallSequenceGeneratorMutationStrategy struct { // CallSequenceGeneratorFunc describes a method used to populate a provided call sequence. CallSequenceGeneratorFunc CallSequenceGeneratorFunc - - // PrefetchModifyCallFunc defines a method used to modify a call sequence element before being fetched. - PrefetchModifyCallFunc PrefetchModifyCallFunc + // Whether to mutate + Mutate bool } // NewCallSequenceGenerator creates a CallSequenceGenerator to generate call sequences for use in fuzzing campaigns. @@ -126,78 +116,41 @@ func NewCallSequenceGenerator(worker *FuzzerWorker, config *CallSequenceGenerato generator.mutationStrategyChooser.AddChoices( randomutils.NewWeightedRandomChoice( CallSequenceGeneratorMutationStrategy{ - CallSequenceGeneratorFunc: callSeqGenFuncCorpusHead, - PrefetchModifyCallFunc: nil, - }, - new(big.Int).SetUint64(config.RandomUnmodifiedCorpusHeadWeight), - ), - randomutils.NewWeightedRandomChoice( - CallSequenceGeneratorMutationStrategy{ - CallSequenceGeneratorFunc: callSeqGenFuncCorpusTail, - PrefetchModifyCallFunc: nil, - }, - new(big.Int).SetUint64(config.RandomUnmodifiedCorpusTailWeight), - ), - randomutils.NewWeightedRandomChoice( - CallSequenceGeneratorMutationStrategy{ - CallSequenceGeneratorFunc: callSeqGenFuncExpansion, - PrefetchModifyCallFunc: nil, - }, - new(big.Int).SetUint64(config.RandomUnmodifiedCorpusTailWeight), - ), - randomutils.NewWeightedRandomChoice( - CallSequenceGeneratorMutationStrategy{ - CallSequenceGeneratorFunc: callSeqGenFuncSpliceAtRandom, - PrefetchModifyCallFunc: nil, + CallSequenceGeneratorFunc: prependFromCorpus, }, - new(big.Int).SetUint64(config.RandomUnmodifiedSpliceAtRandomWeight), + new(big.Int).SetUint64(config.Prepend), ), randomutils.NewWeightedRandomChoice( CallSequenceGeneratorMutationStrategy{ - CallSequenceGeneratorFunc: callSeqGenFuncInterleaveAtRandom, - PrefetchModifyCallFunc: nil, + CallSequenceGeneratorFunc: appendFromCorpus, }, - new(big.Int).SetUint64(config.RandomUnmodifiedInterleaveAtRandomWeight), + new(big.Int).SetUint64(config.Append), ), randomutils.NewWeightedRandomChoice( CallSequenceGeneratorMutationStrategy{ - CallSequenceGeneratorFunc: callSeqGenFuncCorpusHead, - PrefetchModifyCallFunc: prefetchModifyCallFuncMutate, + CallSequenceGeneratorFunc: spliceCorpus, }, - new(big.Int).SetUint64(config.RandomMutatedCorpusHeadWeight), + new(big.Int).SetUint64(config.Splice), ), randomutils.NewWeightedRandomChoice( CallSequenceGeneratorMutationStrategy{ - CallSequenceGeneratorFunc: callSeqGenFuncCorpusTail, - PrefetchModifyCallFunc: prefetchModifyCallFuncMutate, + CallSequenceGeneratorFunc: interleaveCorpus, }, - new(big.Int).SetUint64(config.RandomMutatedCorpusTailWeight), + new(big.Int).SetUint64(config.Interleave), ), randomutils.NewWeightedRandomChoice( CallSequenceGeneratorMutationStrategy{ - CallSequenceGeneratorFunc: callSeqGenFuncSpliceAtRandom, - PrefetchModifyCallFunc: prefetchModifyCallFuncMutate, + CallSequenceGeneratorFunc: prependFromCorpus, + Mutate: true, }, - new(big.Int).SetUint64(config.RandomMutatedSpliceAtRandomWeight), + new(big.Int).SetUint64(config.PrependAndMutate), ), randomutils.NewWeightedRandomChoice( CallSequenceGeneratorMutationStrategy{ - CallSequenceGeneratorFunc: callSeqGenFuncInterleaveAtRandom, - PrefetchModifyCallFunc: prefetchModifyCallFuncMutate, + CallSequenceGeneratorFunc: appendFromCorpus, + Mutate: true, }, - new(big.Int).SetUint64(config.RandomMutatedInterleaveAtRandomWeight), - ), - randomutils.NewWeightedRandomChoice(CallSequenceGeneratorMutationStrategy{ - CallSequenceGeneratorFunc: callSeqSwapRandomElement, - PrefetchModifyCallFunc: nil, - }, - new(big.Int).SetUint64(config.RandomMutatedInterleaveAtRandomWeight), - ), - randomutils.NewWeightedRandomChoice(CallSequenceGeneratorMutationStrategy{ - CallSequenceGeneratorFunc: callSeqDeleteRandomElement, - PrefetchModifyCallFunc: nil, - }, - new(big.Int).SetUint64(config.RandomMutatedInterleaveAtRandomWeight), + new(big.Int).SetUint64(config.AppendAndMutate), ), ) @@ -212,7 +165,6 @@ func (g *CallSequenceGenerator) InitializeNextSequence() (bool, error) { // Reset the state of our generator. g.baseSequence = make(calls.CallSequence, g.worker.fuzzer.config.Fuzzing.CallSequenceLength) g.fetchIndex = 0 - g.prefetchModifyCallFunc = nil // Check if there are any previously un-executed corpus call sequences. If there are, the fuzzer should execute // those first. @@ -242,11 +194,21 @@ func (g *CallSequenceGenerator) InitializeNextSequence() (bool, error) { // If we have a corpus mutation method, call it to generate our base sequence, then set the pre-fetch modify // call function. if corpusMutationFunc != nil && corpusMutationFunc.CallSequenceGeneratorFunc != nil { - err = corpusMutationFunc.CallSequenceGeneratorFunc(g.worker.randomProvider, g.worker.fuzzer.corpus.RandomMutationTargetSequence, g.baseSequence) + fetchFromCorpusAndMutate := func() (calls.CallSequence, error) { + corpusSequence, err := g.worker.fuzzer.corpus.RandomMutationTargetSequence() + if err != nil { + return corpusSequence, fmt.Errorf("could not generate a corpus mutation derived call sequence due to an error obtaining a mutation target: %v", err) + } + if corpusMutationFunc.Mutate { + err = g.mutateCallSequence(corpusSequence) + } + return corpusSequence, err + } + + err = corpusMutationFunc.CallSequenceGeneratorFunc(g.worker.randomProvider, fetchFromCorpusAndMutate, g.baseSequence) if err != nil { return true, fmt.Errorf("could not generate a corpus mutation derived call sequence due to an error executing a mutation method: %v", err) } - g.prefetchModifyCallFunc = corpusMutationFunc.PrefetchModifyCallFunc } } return true, nil @@ -270,16 +232,6 @@ func (g *CallSequenceGenerator) PopSequenceElement() (*calls.CallSequenceElement if err != nil { return nil, err } - } else { - // We have an element, if our generator set a post-call modify for this function, execute it now to modify - // our call prior to return. This allows mutations to be applied on a per-call time frame, rather than - // per-sequence, making use of the most recent runtime data. - if g.prefetchModifyCallFunc != nil { - err = g.prefetchModifyCallFunc(g, element) - if err != nil { - return nil, err - } - } } // Update the element with the current nonce for the associated chain. @@ -369,10 +321,30 @@ func (g *CallSequenceGenerator) generateNewElement() (*calls.CallSequenceElement return calls.NewCallSequenceElement(selectedMethod.Contract, msg, blockNumberDelay, blockTimestampDelay), nil } -// prefetchModifyCallFuncMutate is a PrefetchModifyCallFunc, called by a CallSequenceGenerator to apply mutations -// to a call sequence element, prior to it being fetched. +// mutateCallSequenceElement applies a mutation to a call sequence element, prior to it being fetched from the corpus. // Returns an error if one occurs. -func prefetchModifyCallFuncMutate(sequenceGenerator *CallSequenceGenerator, element *calls.CallSequenceElement) error { +func (g *CallSequenceGenerator) mutateCallSequence(sequence calls.CallSequence) error { + // Equal probability of mutating a random element, expanding a random element, swapping a random element, or deleting a random element. + choice := g.worker.randomProvider.Intn(4) + if choice == 0 { + return g.mutateRandomElement(sequence) + } + if choice == 1 { + return expandRandomElement(g.worker.randomProvider, sequence) + } + if choice == 2 { + return swapRandomElement(g.worker.randomProvider, sequence) + } + if choice == 3 { + return deleteRandomElement(g.worker.randomProvider, sequence) + } + + return nil +} + +func (g *CallSequenceGenerator) mutateRandomElement(sequence calls.CallSequence) error { + element := sequence[g.worker.randomProvider.Intn(len(sequence))] + // If this element has no ABI value based call data, exit early. if element.Call == nil || element.Call.DataAbiValues == nil { return nil @@ -381,7 +353,7 @@ func prefetchModifyCallFuncMutate(sequenceGenerator *CallSequenceGenerator, elem // Loop for each input value and mutate it abiValuesMsgData := element.Call.DataAbiValues for i := 0; i < len(abiValuesMsgData.InputValues); i++ { - mutatedInput, err := valuegeneration.MutateAbiValue(sequenceGenerator.config.ValueGenerator, sequenceGenerator.config.ValueMutator, &abiValuesMsgData.Method.Inputs[i].Type, abiValuesMsgData.InputValues[i]) + mutatedInput, err := valuegeneration.MutateAbiValue(g.config.ValueGenerator, g.config.ValueMutator, &abiValuesMsgData.Method.Inputs[i].Type, abiValuesMsgData.InputValues[i]) if err != nil { return fmt.Errorf("error when mutating call sequence input argument: %v", err) } @@ -393,7 +365,16 @@ func prefetchModifyCallFuncMutate(sequenceGenerator *CallSequenceGenerator, elem return nil } -func callSeqSwapRandomElement(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { +func expandRandomElement(provider *rand.Rand, sequence calls.CallSequence) error { + // Expand the sequence + expandedSequence := expandRandList(provider, sequence) + + copy(sequence, expandedSequence) + + return nil +} + +func swapRandomElement(provider *rand.Rand, sequence calls.CallSequence) error { // Swap the element swappedSequence := swapRandList(provider, sequence) @@ -402,7 +383,7 @@ func callSeqSwapRandomElement(provider *rand.Rand, sequenceGenerator func() (cal return nil } -func callSeqDeleteRandomElement(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { +func deleteRandomElement(provider *rand.Rand, sequence calls.CallSequence) error { // Delete the element deletedSequence := deleteRandList(provider, sequence) @@ -411,63 +392,46 @@ func callSeqDeleteRandomElement(provider *rand.Rand, sequenceGenerator func() (c return nil } -// callSeqGenFuncCorpusHead is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a sequence +// prependFromCorpus is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a sequence // whose head is based off of an existing corpus call sequence. // Returns an error if one occurs. -func callSeqGenFuncCorpusHead(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { +func prependFromCorpus(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { // Obtain a call sequence from the corpus corpusSequence, err := sequenceGenerator() if err != nil { return fmt.Errorf("could not obtain corpus call sequence for head mutation: %v", err) } - // Prepend the new calls to the end of the corpus sequence - i := provider.Intn(len(corpusSequence)) + 1 - spliced := append(corpusSequence[:i], sequence...) - - copy(sequence, spliced) + // Determine the length of the slice to be copied in the head. + maxLength := utils.Min(len(sequence), len(corpusSequence)) + copy(sequence, corpusSequence[:maxLength]) return nil } -// callSeqGenFuncCorpusTail is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a sequence +// appendFromCorpus is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a sequence // whose tail is based off of an existing corpus call sequence. // Returns an error if one occurs. -func callSeqGenFuncCorpusTail(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { +func appendFromCorpus(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { // Obtain a call sequence from the corpus corpusSequence, err := sequenceGenerator() if err != nil { return fmt.Errorf("could not obtain corpus call sequence for tail mutation: %v", err) } + // Determine a random position to slice the call sequence. maxLength := utils.Min(len(sequence), len(corpusSequence)) - i := provider.Intn(maxLength) + 1 - fmt.Println("i: ", i) - // Append the new calls to the end of the corpus sequence - spliced := append(sequence[i:], corpusSequence...) - - copy(sequence, spliced) - - return nil -} - -// callSeqGenFuncExpansion is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a -// sequence which is expanded up to 30 times by replicating an existing call sequence element at a random position. -func callSeqGenFuncExpansion(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { - - // Expand the sequence - expandedSequence := expandRandList(provider, sequence) - - copy(sequence, expandedSequence) + targetLength := provider.Intn(maxLength) + 1 + copy(sequence[len(sequence)-targetLength:], corpusSequence[len(corpusSequence)-targetLength:]) return nil } -// callSeqGenFuncSpliceAtRandom is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a +// spliceCorpus is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a // sequence which is based off of two corpus call sequence entries, from which a random length head and tail are // respectively sliced and joined together. // Returns an error if one occurs. -func callSeqGenFuncSpliceAtRandom(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { +func spliceCorpus(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { // Obtain two corpus call sequence entries headSequence, err := sequenceGenerator() if err != nil { @@ -486,11 +450,11 @@ func callSeqGenFuncSpliceAtRandom(provider *rand.Rand, sequenceGenerator func() return nil } -// callSeqGenFuncInterleaveAtRandom is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a +// interleaveCorpus is a CallSequenceGeneratorFunc which prepares a CallSequenceGenerator to generate a // sequence which is based off of two corpus call sequence entries, from which a random number of transactions are // taken and interleaved (each element of one sequence will be followed by an element of the other). // Returns an error if one occurs. -func callSeqGenFuncInterleaveAtRandom(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { +func interleaveCorpus(provider *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error { // Obtain two corpus call sequence entries firstSequence, err := sequenceGenerator() if err != nil { @@ -533,7 +497,7 @@ func expandRandList[T any](provider *rand.Rand, xs []*T) []*T { return xs } k := provider.Intn(l) - t := provider.Intn(min(32, l)) + 1 + t := provider.Intn(utils.Min(32, l)) + 1 return expandAt(xs, k, t) } @@ -564,7 +528,7 @@ func swapRandList[T any](provider *rand.Rand, xs []*T) []*T { return xs } i, j := rand.Intn(len(xs)), rand.Intn(len(xs)) - return swapAt(xs, min(i, j), max(i, j)) + return swapAt(xs, utils.Min(i, j), utils.Max(i, j)) } // spliceAtRandom splices two lists at random positions. @@ -589,19 +553,3 @@ func interleaveLL[T any](a, b []*T) []*T { } return append([]*T{a[0], b[0]}, interleaveLL(a[1:], b[1:])...) } - -// min returns the smaller of two integers. -func min(a, b int) int { - if a < b { - return a - } - return b -} - -// max returns the larger of two integers. -func max(a, b int) int { - if a > b { - return a - } - return b -} diff --git a/fuzzing/sequence_generator_test.go b/fuzzing/sequence_generator_test.go index 2466a941..0fe2ad0d 100644 --- a/fuzzing/sequence_generator_test.go +++ b/fuzzing/sequence_generator_test.go @@ -52,13 +52,10 @@ func getMockCallSequenceElementCall(data int) *calls.CallMessage { func TestSplice(t *testing.T) { strategies := map[string]func(rand *rand.Rand, sequenceGenerator func() (calls.CallSequence, error), sequence calls.CallSequence) error{ - // "interleave": callSeqGenFuncInterleaveAtRandom, - // "splice": callSeqGenFuncSpliceAtRandom, - // "expansion": callSeqGenFuncExpansion, - "prepend": callSeqGenFuncCorpusHead, - "apppend": callSeqGenFuncCorpusTail, - // "delete": callSeqDeleteRandomElement, - // "swap": callSeqSwapRandomElement, + "interleave": interleaveCorpus, + "splice": spliceCorpus, + "prepend": prependFromCorpus, + "append": appendFromCorpus, } // Seed the PRNG to make the randomness deterministic @@ -66,10 +63,9 @@ func TestSplice(t *testing.T) { // expectedSize := 8 // Adjust based on the expected interleave size in your scenario // Prepare a source sequence (with the expected size) - for name, strategyFn := range strategies { // Call the function under test - sourceSequence := []calls.CallSequence{getMockCallSequence(1, 1), getMockCallSequence(1, 5)} + sourceSequence := []calls.CallSequence{getMockCallSequence(4, 1), getMockCallSequence(4, 5)} i := 0 sequence := getMockCallSequence(4, 7) sequence = append(sequence, getMockCallSequence(4, 9)...) From 9d95a8e522366207a29cf547d7f295eb75a8f7e0 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Fri, 6 Sep 2024 13:01:36 -0500 Subject: [PATCH 5/5] switch weight --- fuzzing/fuzzer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index b7313bb1..31332c09 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -570,8 +570,8 @@ func defaultCallSequenceGeneratorConfigFunc(fuzzer *Fuzzer, valueSet *valuegener // Create a sequence generator config which uses the created value generator. sequenceGenConfig := &CallSequenceGeneratorConfig{ NewSequenceProbability: 0.3, - Prepend: 800, - Append: 200, + Append: 800, + Prepend: 200, Splice: 100, Interleave: 100, PrependAndMutate: 100,