Skip to content

Commit 46f758b

Browse files
authored
Merge branch 'master' into dependabot/go_modules/github.com/holiman/uint256-1.3.2
2 parents 782e67f + fcdf86e commit 46f758b

File tree

9 files changed

+243
-207
lines changed

9 files changed

+243
-207
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,12 @@ jobs:
8282
8383
- name: Sign artifact
8484
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
85-
uses: sigstore/gh-action-sigstore-python@v3.0.1
85+
uses: sigstore/gh-action-sigstore-python@v3.1.0
8686
with:
8787
inputs: ./medusa-*.tar.gz
8888

8989
- name: Upload artifact
90-
uses: actions/upload-artifact@v4
90+
uses: actions/upload-artifact@v5
9191
with:
9292
name: medusa-${{ runner.os }}-${{ runner.arch }}
9393
path: |
@@ -105,7 +105,7 @@ jobs:
105105

106106
steps:
107107
- name: Download binaries
108-
uses: actions/download-artifact@v5
108+
uses: actions/download-artifact@v6
109109
with:
110110
pattern: medusa-*
111111
merge-multiple: true

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
## medusa build process
2-
FROM golang:1.23 AS medusa
2+
FROM golang:1.24 AS medusa
33

44
WORKDIR /src
55
COPY . /src/medusa/

fuzzing/corpus/corpus.go

Lines changed: 100 additions & 161 deletions
Large diffs are not rendered by default.

fuzzing/corpus/corpus_files.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ package corpus
33
import (
44
"encoding/json"
55
"fmt"
6-
"github.com/crytic/medusa/utils"
76
"os"
87
"path/filepath"
98
"strings"
109
"sync"
10+
11+
"github.com/crytic/medusa/utils"
1112
)
1213

1314
// corpusFile represents corpus data and its state on the filesystem.
@@ -176,7 +177,7 @@ func (cd *corpusDirectory[T]) writeFiles() error {
176177
// Write the JSON encoded data.
177178
err = os.WriteFile(filePath, jsonEncodedData, os.ModePerm)
178179
if err != nil {
179-
return fmt.Errorf("An error occurred while writing corpus data to file: %v\n", err)
180+
return fmt.Errorf("an error occurred while writing corpus data to file: %v", err)
180181
}
181182

182183
// Update our written to disk status.

fuzzing/fuzzer.go

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,7 @@ func (f *Fuzzer) spawnWorkersLoop(baseTestChain *chain.TestChain) error {
814814
if err == nil && workerCreatedErr != nil {
815815
err = workerCreatedErr
816816
}
817+
817818
if err == nil {
818819
// Publish an event indicating we created a worker.
819820
workerCreatedErr = f.Events.WorkerCreated.Publish(FuzzerWorkerCreatedEvent{Worker: worker})
@@ -894,13 +895,6 @@ func (f *Fuzzer) Start() error {
894895
f.ctx, f.ctxCancelFunc = context.WithTimeout(f.ctx, time.Duration(f.config.Fuzzing.Timeout)*time.Second)
895896
}
896897

897-
// Set up the corpus
898-
f.logger.Info("Initializing corpus")
899-
f.corpus, err = corpus.NewCorpus(f.config.Fuzzing.CorpusDirectory)
900-
if err != nil {
901-
f.logger.Error("Failed to create the corpus", err)
902-
return err
903-
}
904898
// Start the revert reporter
905899
f.revertReporter.Start(f.ctx)
906900

@@ -933,28 +927,25 @@ func (f *Fuzzer) Start() error {
933927
}
934928
f.logger.Info("Finished setting up test chain")
935929

936-
// Initialize our coverage maps by measuring the coverage we get from the corpus.
937-
var corpusActiveSequences, corpusTotalSequences int
938-
if totalCallSequences, testResults := f.corpus.CallSequenceEntryCount(); totalCallSequences > 0 || testResults > 0 {
939-
f.logger.Info("Running call sequences in the corpus")
940-
}
941-
startTime := time.Now()
942-
corpusActiveSequences, corpusTotalSequences, err = f.corpus.Initialize(baseTestChain, f.contractDefinitions)
943-
if corpusTotalSequences > 0 {
944-
f.logger.Info("Finished running call sequences in the corpus in ", time.Since(startTime).Round(time.Second))
930+
// Create and initialize the corpus
931+
f.logger.Info("Creating corpus...")
932+
f.corpus, err = corpus.NewCorpus(f.config.Fuzzing.CorpusDirectory)
933+
if err != nil {
934+
f.logger.Error("Failed to create the corpus", err)
935+
return err
945936
}
937+
err = f.corpus.Initialize(baseTestChain, f.contractDefinitions)
946938
if err != nil {
947939
f.logger.Error("Failed to initialize the corpus", err)
948940
return err
949941
}
950942

951-
// Log corpus health statistics, if we have any existing sequences.
952-
if corpusTotalSequences > 0 {
953-
f.logger.Info(
954-
colors.Bold, "corpus: ", colors.Reset,
955-
"health: ", colors.Bold, int(float32(corpusActiveSequences)/float32(corpusTotalSequences)*100.0), "%", colors.Reset, ", ",
956-
"sequences: ", colors.Bold, corpusTotalSequences, " (", corpusActiveSequences, " valid, ", corpusTotalSequences-corpusActiveSequences, " invalid)", colors.Reset,
957-
)
943+
// Log that we will initialize corpus if there are any call sequences or test results
944+
if totalCallSequences, testResults := f.corpus.CallSequenceEntryCount(); totalCallSequences > 0 || testResults > 0 {
945+
f.logger.Info("Initializing corpus...")
946+
947+
// Monitor corpus initialization
948+
go f.monitorCorpusInitialization()
958949
}
959950

960951
// Start the corpus pruner.
@@ -1081,6 +1072,45 @@ func (f *Fuzzer) Terminate() {
10811072
}
10821073
}
10831074

1075+
// monitorCorpusInitialization monitors the corpus initialization process and logs the corpus health when it is complete.
1076+
// This goroutine is short-lived and exits when the corpus is initialized.
1077+
func (f *Fuzzer) monitorCorpusInitialization() {
1078+
// There is nothing to do if there are no corpus elements or unexecuted call sequences
1079+
totalSequences, totalTestResults := f.corpus.CallSequenceEntryCount()
1080+
if !f.corpus.InitializingCorpus() || totalSequences == 0 || totalTestResults == 0 {
1081+
return
1082+
}
1083+
1084+
// Capture an approximate start time
1085+
startTime := time.Now()
1086+
for !utils.CheckContextDone(f.ctx) && !utils.CheckContextDone(f.emergencyCtx) {
1087+
// Go to sleep if corpus is still initializing
1088+
if f.corpus.InitializingCorpus() {
1089+
time.Sleep(200 * time.Millisecond)
1090+
continue
1091+
}
1092+
1093+
// Calculate the necessary variables for corpus health
1094+
totalSequences, totalTestResults := f.corpus.CallSequenceEntryCount()
1095+
totalCorpusEntries := totalSequences + totalTestResults
1096+
validSequences := f.corpus.ValidCallSequences()
1097+
invalidSequences := int(totalSequences - int(validSequences))
1098+
1099+
// Log how much time it took to initialize the corpus
1100+
f.logger.Info("Finished running call sequences in the corpus in ", time.Since(startTime).Round(time.Second))
1101+
1102+
// Log the overall corpus health
1103+
f.logger.Info(
1104+
colors.Bold, "corpus: ", colors.Reset,
1105+
"health: ", colors.Bold, int(float32(validSequences)/float32(totalCorpusEntries)*100), "%", colors.Reset, ", ",
1106+
"sequences: ", colors.Bold, totalCorpusEntries, " (", validSequences, " valid, ", invalidSequences, " invalid)", colors.Reset,
1107+
)
1108+
1109+
// Now we can exit the goroutine
1110+
break
1111+
}
1112+
}
1113+
10841114
// printMetricsLoop prints metrics to the console in a loop until ctx signals a stopped operation.
10851115
func (f *Fuzzer) printMetricsLoop() {
10861116
// Define our start time

fuzzing/fuzzer_worker.go

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,46 @@ func (fw *FuzzerWorker) updateMethods() {
259259
}
260260
}
261261

262+
// bindCorpusElement ensures that the de-serialized corpus element is ready for runtime use.
263+
// The index for the element is provided and the base sequence used for execution is updated in-place.
264+
// It resolves the contract definition and ABI metadata needed for runtime execution. If the function
265+
// returns an error, the call sequence/corpus item is marked as invalid and will not be used for mutations.
266+
func (fw *FuzzerWorker) bindCorpusElement(currentIndex int) error {
267+
// Guard clause
268+
if currentIndex >= len(fw.sequenceGenerator.baseSequence) {
269+
return nil
270+
}
271+
272+
// Obtain the corpus element
273+
element := fw.sequenceGenerator.baseSequence[currentIndex]
274+
275+
// If it is a contract creation, there is nothing to do.
276+
if element.Call.To == nil {
277+
return nil
278+
}
279+
280+
contractDefinition, ok := fw.deployedContracts[*element.Call.To]
281+
if !ok {
282+
return fmt.Errorf("contract at address %v could not be resolved", element.Call.To.String())
283+
}
284+
element.Contract = contractDefinition
285+
286+
// Next, if our sequence element uses ABI values to produce call data, our deserialized data is not yet
287+
// sufficient for runtime use, until we use it to resolve runtime references.
288+
if abiValues := element.Call.DataAbiValues; abiValues != nil {
289+
// Resolve the ABI values.
290+
if err := abiValues.Resolve(contractDefinition.CompiledContract().Abi); err != nil {
291+
return fmt.Errorf("error resolving method in contract '%v': %v", element.Contract.Name(), err)
292+
}
293+
}
294+
295+
return nil
296+
}
297+
262298
// testNextCallSequence tests a call message sequence against the underlying FuzzerWorker's Chain and calls every
263299
// CallSequenceTestFunc registered with the parent Fuzzer to update any test results. If any call message in the
264300
// sequence is nil, a call message will be created in its place, targeting a state changing method of a contract
265-
// deployed in the Chain.
301+
// deployed in the Chain. Note that this function also replays the corpus call sequences before entering the main fuzzing loop.
266302
// Returns any requests for call sequence shrinking or an error if one occurs.
267303
func (fw *FuzzerWorker) testNextCallSequence() ([]ShrinkCallSequenceRequest, error) {
268304
// We will make a copy of the worker's base value set so that we can rollback to it at the end of the call sequence
@@ -290,6 +326,17 @@ func (fw *FuzzerWorker) testNextCallSequence() ([]ShrinkCallSequenceRequest, err
290326

291327
// Our "fetch next call" method will generate new calls as needed, if we are generating a new sequence.
292328
fetchElementFunc := func(currentIndex int) (*calls.CallSequenceElement, error) {
329+
// We need to prepare the corpus element for runtime execution if we are replaying a corpus sequence
330+
if !isNewSequence {
331+
err := fw.bindCorpusElement(currentIndex)
332+
333+
// We will separately capture the error and log that the corpus element is disabled
334+
if err != nil {
335+
return nil, err
336+
}
337+
}
338+
339+
// Now, we are ready to fetch the next element from the sequence generator
293340
return fw.sequenceGenerator.PopSequenceElement()
294341
}
295342

@@ -340,10 +387,16 @@ func (fw *FuzzerWorker) testNextCallSequence() ([]ShrinkCallSequenceRequest, err
340387
}
341388

342389
// Execute our call sequence.
343-
_, err = calls.ExecuteCallSequenceIteratively(fw.chain, fetchElementFunc, executionCheckFunc)
390+
var executedSequence calls.CallSequence
391+
executedSequence, err = calls.ExecuteCallSequenceIteratively(fw.chain, fetchElementFunc, executionCheckFunc)
344392

345393
// If we encountered an error, report it.
346394
if err != nil {
395+
// If a corpus element fails to execute, log it (in debug mode) and exit the function early
396+
if !isNewSequence {
397+
fw.fuzzer.logger.Debug("[Worker ", fw.workerIndex, "] corpus element has been disabled due to an error:", err)
398+
return nil, nil
399+
}
347400
return nil, err
348401
}
349402

@@ -352,7 +405,17 @@ func (fw *FuzzerWorker) testNextCallSequence() ([]ShrinkCallSequenceRequest, err
352405
return nil, nil
353406
}
354407

355-
// If this was not a new call sequence, indicate not to save the shrunken result to the corpus again.
408+
// We successfully executed a corpus element
409+
if !isNewSequence {
410+
fw.fuzzer.corpus.IncrementValid()
411+
// If there are no shrink requests that means this is not a test result call sequence, so we can mark it for mutation.
412+
if len(shrinkCallSequenceRequests) == 0 {
413+
// We don't really want an error here to stop fuzzing, so we ignore it.
414+
_ = fw.fuzzer.corpus.MarkCallSequenceForMutation(executedSequence, big.NewInt(1))
415+
}
416+
}
417+
418+
// We don't want to save shrink results from corpus sequences since we already did.
356419
if !isNewSequence {
357420
for i := 0; i < len(fw.shrinkCallSequenceRequests); i++ {
358421
shrinkCallSequenceRequests[i].RecordResultInCorpus = false

fuzzing/fuzzer_worker_sequence_generator.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,11 @@ func (g *CallSequenceGenerator) PopSequenceElement() (*calls.CallSequenceElement
262262
}
263263
}
264264

265-
// Update the element with the current nonce for the associated chain.
266-
element.Call.FillFromTestChainProperties(g.worker.chain)
265+
// Update the element with the current nonce for the associated chain only if we are not replaying a corpus sequence.
266+
// TODO: This feels a little hacky
267+
if !g.worker.fuzzer.corpus.InitializingCorpus() {
268+
element.Call.FillFromTestChainProperties(g.worker.chain)
269+
}
267270

268271
// Update our base sequence, advance our position, and return the processed element from this round.
269272
g.baseSequence[g.fetchIndex] = element

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ require (
1212
github.com/pkg/errors v0.9.1
1313
github.com/rs/zerolog v1.34.0
1414
github.com/shopspring/decimal v1.4.0
15-
github.com/spf13/cobra v1.9.1
16-
github.com/spf13/pflag v1.0.6
17-
github.com/stretchr/testify v1.10.0
18-
go.etcd.io/bbolt v1.4.0
15+
github.com/spf13/cobra v1.10.1
16+
github.com/spf13/pflag v1.0.9
17+
github.com/stretchr/testify v1.11.1
18+
go.etcd.io/bbolt v1.4.3
1919
golang.org/x/crypto v0.43.0
2020
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
2121
golang.org/x/net v0.46.0

go.sum

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,10 @@ github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKl
182182
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
183183
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
184184
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
185-
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
186-
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
187-
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
188-
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
185+
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
186+
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
187+
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
188+
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
189189
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
190190
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
191191
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -194,8 +194,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
194194
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
195195
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
196196
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
197-
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
198-
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
197+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
198+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
199199
github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo=
200200
github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
201201
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
@@ -212,8 +212,8 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY
212212
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
213213
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
214214
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
215-
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
216-
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
215+
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
216+
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
217217
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
218218
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
219219
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=

0 commit comments

Comments
 (0)