From 658e4e5559547a63180dc5665aaf3b4cdf45e6e5 Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Thu, 5 Sep 2024 15:52:50 -0600 Subject: [PATCH] Fix EOF Validation issues Fixes a few EOF validation issues in EEST and found by fuzzing. * subcontainers header with zero subcontainers is invalid * returning code sections require at least one RETF or qualified JUMPF * RETF stack must have stack enough for outputs * JUMPF must not jump into a section with more outputs * Check stack minimums on RJIMPI and RJUMPV --- cmd/eofdump/eofparser.go | 6 +++--- core/vm/eof.go | 3 +++ core/vm/errors.go | 1 + core/vm/validate_linear.go | 19 +++++++++++++++++-- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/cmd/eofdump/eofparser.go b/cmd/eofdump/eofparser.go index 483a730b08..4831139c0e 100644 --- a/cmd/eofdump/eofparser.go +++ b/cmd/eofdump/eofparser.go @@ -170,7 +170,7 @@ func ExecuteTest(src []byte) (int, int, error) { return 0, 0, err } passed, total := 0, 0 - for _, tests := range testsByName { + for testsName, tests := range testsByName { for name, tt := range tests.Vectors { for fork, r := range tt.Results { total++ @@ -181,11 +181,11 @@ func ExecuteTest(src []byte) (int, int, error) { err = err2 } if r.Result && err != nil { - fmt.Fprintf(os.Stderr, "%s, %s: expected success, got %v\n", name, fork, err) + fmt.Fprintf(os.Stderr, "%s %s, %s: expected success, got %v\n", testsName, name, fork, err) continue } if !r.Result && err == nil { - fmt.Fprintf(os.Stderr, "%s, %s: expected error %s, got %v\n", name, fork, r.Exception, err) + fmt.Fprintf(os.Stderr, "%s %s, %s: expected error %s, got %v\n", testsName, name, fork, r.Exception, err) continue } /* diff --git a/core/vm/eof.go b/core/vm/eof.go index c8c87930e2..7b005ca326 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -206,6 +206,9 @@ func (c *Container) unmarshalSubContainer(b []byte, isInitcode bool, topLevel bo if kind != kindContainer { panic("somethings wrong") } + if len(containerSizes) == 0 { + return fmt.Errorf("%w: total container count must not be zero", ErrInvalidContainerSectionSize) + } offset = offset + 2 + 2*len(containerSizes) + 1 } diff --git a/core/vm/errors.go b/core/vm/errors.go index 429ab1415b..95534ab96d 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -42,6 +42,7 @@ var ( ErrInvalidEOFInitcode = errors.New("invalid eof initcode") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") ErrInvalidNumberOfOutputs = errors.New("invalid number of outputs") + ErrInvalidNonReturningFlag = errors.New("Invalid non-returning flag, bad RETF") // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. diff --git a/core/vm/validate_linear.go b/core/vm/validate_linear.go index f5e7b18576..b1fc092296 100644 --- a/core/vm/validate_linear.go +++ b/core/vm/validate_linear.go @@ -26,6 +26,7 @@ func validateControlFlow2(code []byte, section int, metadata []*FunctionMetadata // set the initial stack bounds setBounds(0, int(metadata[section].Input), int(metadata[section].Input)) + qualifiedExit := false for pos := 0; pos < len(code); pos++ { op := OpCode(code[pos]) currentBounds := stackBounds[pos] @@ -62,9 +63,14 @@ func validateControlFlow2(code []byte, section int, metadata []*FunctionMetadata if currentBounds.max != currentBounds.min { return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidNumberOfOutputs, currentBounds.max, currentBounds.min, pos) } - if have, want := int(metadata[section].Output), currentBounds.min; have != want { + have := int(metadata[section].Output) + if have >= maxOutputItems { + return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNonReturningFlag, pos) + } + if want := currentBounds.min; have != want { return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", ErrInvalidOutputs, have, want, pos) } + qualifiedExit = true case JUMPF: arg, _ := parseUint16(code[pos+1:]) newSection := metadata[arg] @@ -76,6 +82,9 @@ func validateControlFlow2(code []byte, section int, metadata []*FunctionMetadata return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) } } else { + if metadata[section].Output < newSection.Output { + return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNumberOfOutputs, pos) + } if currentBounds.max != currentBounds.min { return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidNumberOfOutputs, currentBounds.max, currentBounds.min, pos) } @@ -83,6 +92,7 @@ func validateControlFlow2(code []byte, section int, metadata []*FunctionMetadata return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNumberOfOutputs, pos) } } + qualifiedExit = qualifiedExit || newSection.Output < maxOutputItems case DUPN: arg := int(code[pos+1]) + 1 if want, have := arg, currentBounds.min; want > have { @@ -175,12 +185,14 @@ func validateControlFlow2(code []byte, section int, metadata []*FunctionMetadata // target reached via backwards jump nextBounds, ok := stackBounds[nextPC] if !ok { - fmt.Print("2") return 0, ErrInvalidBackwardJump } if currentStackMax != nextBounds.max { return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", ErrInvalidBackwardJump, currentStackMax, nextBounds.max, pos) } + if currentStackMin != nextBounds.min { + return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", ErrInvalidBackwardJump, currentStackMin, nextBounds.min, pos) + } } } } @@ -192,6 +204,9 @@ func validateControlFlow2(code []byte, section int, metadata []*FunctionMetadata } } + if qualifiedExit != (metadata[section].Output < maxOutputItems) { + return 0, fmt.Errorf("%w no RETF or qualified JUMPF", ErrInvalidNonReturningFlag) + } if maxStackHeight >= int(params.StackLimit) { return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)} }