Merge commit from fork

* Limit recursion depth for unknown field detection

(cherry picked from commit f038dc731c55be1e1c526e67695acc358631afd6)

* Limit unpack any

(cherry picked from commit 1a2bff56fb7391f9ce87d4fbe9e0367ae991c0b2)

* Update Changelog

* Another limit recursion depth for unknown field detection

* Update changelog
This commit is contained in:
Alexander Peters
2024-12-16 17:06:10 +01:00
committed by GitHub
parent 679ca5efcd
commit ba7ac458c9
5 changed files with 96 additions and 7 deletions
+55 -2
View File
@@ -1,6 +1,7 @@
package types
import (
"errors"
"fmt"
"reflect"
@@ -12,6 +13,17 @@ import (
"cosmossdk.io/x/tx/signing"
)
var (
// MaxUnpackAnySubCalls extension point that defines the maximum number of sub-calls allowed during the unpacking
// process of protobuf Any messages.
MaxUnpackAnySubCalls = 100
// MaxUnpackAnyRecursionDepth extension point that defines the maximum allowed recursion depth during protobuf Any
// message unpacking.
MaxUnpackAnyRecursionDepth = 10
)
// AnyUnpacker is an interface which allows safely unpacking types packed
// in Any's against a whitelist of registered types
type AnyUnpacker interface {
@@ -270,6 +282,45 @@ func (registry *interfaceRegistry) ListImplementations(ifaceName string) []strin
}
func (registry *interfaceRegistry) UnpackAny(any *Any, iface interface{}) error {
unpacker := &statefulUnpacker{
registry: registry,
maxDepth: MaxUnpackAnyRecursionDepth,
maxCalls: &sharedCounter{count: MaxUnpackAnySubCalls},
}
return unpacker.UnpackAny(any, iface)
}
// sharedCounter is a type that encapsulates a counter value
type sharedCounter struct {
count int
}
// statefulUnpacker is a struct that helps in deserializing and unpacking
// protobuf Any messages while maintaining certain stateful constraints.
type statefulUnpacker struct {
registry *interfaceRegistry
maxDepth int
maxCalls *sharedCounter
}
// cloneForRecursion returns a new statefulUnpacker instance with maxDepth reduced by one, preserving the registry and maxCalls.
func (r statefulUnpacker) cloneForRecursion() *statefulUnpacker {
return &statefulUnpacker{
registry: r.registry,
maxDepth: r.maxDepth - 1,
maxCalls: r.maxCalls,
}
}
// UnpackAny deserializes a protobuf Any message into the provided interface, ensuring the interface is a pointer.
// It applies stateful constraints such as max depth and call limits, and unpacks interfaces if required.
func (r *statefulUnpacker) UnpackAny(any *Any, iface interface{}) error {
if r.maxDepth == 0 {
return errors.New("max depth exceeded")
}
if r.maxCalls.count == 0 {
return errors.New("call limit exceeded")
}
// here we gracefully handle the case in which `any` itself is `nil`, which may occur in message decoding
if any == nil {
return nil
@@ -280,6 +331,8 @@ func (registry *interfaceRegistry) UnpackAny(any *Any, iface interface{}) error
return nil
}
r.maxCalls.count--
rv := reflect.ValueOf(iface)
if rv.Kind() != reflect.Ptr {
return fmt.Errorf("UnpackAny expects a pointer")
@@ -295,7 +348,7 @@ func (registry *interfaceRegistry) UnpackAny(any *Any, iface interface{}) error
}
}
imap, found := registry.interfaceImpls[rt]
imap, found := r.registry.interfaceImpls[rt]
if !found {
return fmt.Errorf("no registered implementations of type %+v", rt)
}
@@ -315,7 +368,7 @@ func (registry *interfaceRegistry) UnpackAny(any *Any, iface interface{}) error
return err
}
err = UnpackInterfaces(msg, registry)
err = UnpackInterfaces(msg, r.cloneForRecursion())
if err != nil {
return err
}