Skip to content

Commit ba7ac45

Browse files
authored
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
1 parent 679ca5e commit ba7ac45

File tree

5 files changed

+96
-7
lines changed

5 files changed

+96
-7
lines changed

Diff for: CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
3838

3939
## [Unreleased]
4040

41-
## [v0.50.11](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.50.11) - 2024-12-04
41+
42+
## [v0.50.11](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.50.11) - 2024-12-16
4243

4344
### Features
4445

@@ -50,6 +51,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
5051

5152
### Bug Fixes
5253

54+
* Fix [ABS-0043/ABS-0044](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-8wcc-m6j2-qxvm) Limit recursion depth for unknown field detection and unpack any
5355
* (server) [#22564](https://github.com/cosmos/cosmos-sdk/pull/22564) Fix fallback genesis path in server
5456
* (x/group) [#22425](https://github.com/cosmos/cosmos-sdk/pull/22425) Proper address rendering in error
5557
* (sims) [#21906](https://github.com/cosmos/cosmos-sdk/pull/21906) Skip sims test when running dry on validators

Diff for: codec/types/interface_registry.go

+55-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package types
22

33
import (
4+
"errors"
45
"fmt"
56
"reflect"
67

@@ -12,6 +13,17 @@ import (
1213
"cosmossdk.io/x/tx/signing"
1314
)
1415

16+
var (
17+
18+
// MaxUnpackAnySubCalls extension point that defines the maximum number of sub-calls allowed during the unpacking
19+
// process of protobuf Any messages.
20+
MaxUnpackAnySubCalls = 100
21+
22+
// MaxUnpackAnyRecursionDepth extension point that defines the maximum allowed recursion depth during protobuf Any
23+
// message unpacking.
24+
MaxUnpackAnyRecursionDepth = 10
25+
)
26+
1527
// AnyUnpacker is an interface which allows safely unpacking types packed
1628
// in Any's against a whitelist of registered types
1729
type AnyUnpacker interface {
@@ -270,6 +282,45 @@ func (registry *interfaceRegistry) ListImplementations(ifaceName string) []strin
270282
}
271283

272284
func (registry *interfaceRegistry) UnpackAny(any *Any, iface interface{}) error {
285+
unpacker := &statefulUnpacker{
286+
registry: registry,
287+
maxDepth: MaxUnpackAnyRecursionDepth,
288+
maxCalls: &sharedCounter{count: MaxUnpackAnySubCalls},
289+
}
290+
return unpacker.UnpackAny(any, iface)
291+
}
292+
293+
// sharedCounter is a type that encapsulates a counter value
294+
type sharedCounter struct {
295+
count int
296+
}
297+
298+
// statefulUnpacker is a struct that helps in deserializing and unpacking
299+
// protobuf Any messages while maintaining certain stateful constraints.
300+
type statefulUnpacker struct {
301+
registry *interfaceRegistry
302+
maxDepth int
303+
maxCalls *sharedCounter
304+
}
305+
306+
// cloneForRecursion returns a new statefulUnpacker instance with maxDepth reduced by one, preserving the registry and maxCalls.
307+
func (r statefulUnpacker) cloneForRecursion() *statefulUnpacker {
308+
return &statefulUnpacker{
309+
registry: r.registry,
310+
maxDepth: r.maxDepth - 1,
311+
maxCalls: r.maxCalls,
312+
}
313+
}
314+
315+
// UnpackAny deserializes a protobuf Any message into the provided interface, ensuring the interface is a pointer.
316+
// It applies stateful constraints such as max depth and call limits, and unpacks interfaces if required.
317+
func (r *statefulUnpacker) UnpackAny(any *Any, iface interface{}) error {
318+
if r.maxDepth == 0 {
319+
return errors.New("max depth exceeded")
320+
}
321+
if r.maxCalls.count == 0 {
322+
return errors.New("call limit exceeded")
323+
}
273324
// here we gracefully handle the case in which `any` itself is `nil`, which may occur in message decoding
274325
if any == nil {
275326
return nil
@@ -280,6 +331,8 @@ func (registry *interfaceRegistry) UnpackAny(any *Any, iface interface{}) error
280331
return nil
281332
}
282333

334+
r.maxCalls.count--
335+
283336
rv := reflect.ValueOf(iface)
284337
if rv.Kind() != reflect.Ptr {
285338
return fmt.Errorf("UnpackAny expects a pointer")
@@ -295,7 +348,7 @@ func (registry *interfaceRegistry) UnpackAny(any *Any, iface interface{}) error
295348
}
296349
}
297350

298-
imap, found := registry.interfaceImpls[rt]
351+
imap, found := r.registry.interfaceImpls[rt]
299352
if !found {
300353
return fmt.Errorf("no registered implementations of type %+v", rt)
301354
}
@@ -315,7 +368,7 @@ func (registry *interfaceRegistry) UnpackAny(any *Any, iface interface{}) error
315368
return err
316369
}
317370

318-
err = UnpackInterfaces(msg, registry)
371+
err = UnpackInterfaces(msg, r.cloneForRecursion())
319372
if err != nil {
320373
return err
321374
}

Diff for: codec/unknownproto/unknown_fields.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,23 @@ func RejectUnknownFieldsStrict(bz []byte, msg proto.Message, resolver jsonpb.Any
4040
// This function traverses inside of messages nested via google.protobuf.Any. It does not do any deserialization of the proto.Message.
4141
// An AnyResolver must be provided for traversing inside google.protobuf.Any's.
4242
func RejectUnknownFields(bz []byte, msg proto.Message, allowUnknownNonCriticals bool, resolver jsonpb.AnyResolver) (hasUnknownNonCriticals bool, err error) {
43+
// recursion limit with same default as https://github.com/protocolbuffers/protobuf-go/blob/v1.35.2/encoding/protowire/wire.go#L28
44+
return doRejectUnknownFields(bz, msg, allowUnknownNonCriticals, resolver, 10_000)
45+
}
46+
47+
func doRejectUnknownFields(
48+
bz []byte,
49+
msg proto.Message,
50+
allowUnknownNonCriticals bool,
51+
resolver jsonpb.AnyResolver,
52+
recursionLimit int,
53+
) (hasUnknownNonCriticals bool, err error) {
4354
if len(bz) == 0 {
4455
return hasUnknownNonCriticals, nil
4556
}
57+
if recursionLimit == 0 {
58+
return false, errors.New("recursion limit reached")
59+
}
4660

4761
desc, ok := msg.(descriptorIface)
4862
if !ok {
@@ -130,7 +144,7 @@ func RejectUnknownFields(bz []byte, msg proto.Message, allowUnknownNonCriticals
130144

131145
if protoMessageName == ".google.protobuf.Any" {
132146
// Firstly typecheck types.Any to ensure nothing snuck in.
133-
hasUnknownNonCriticalsChild, err := RejectUnknownFields(fieldBytes, (*types.Any)(nil), allowUnknownNonCriticals, resolver)
147+
hasUnknownNonCriticalsChild, err := doRejectUnknownFields(fieldBytes, (*types.Any)(nil), allowUnknownNonCriticals, resolver, recursionLimit-1)
134148
hasUnknownNonCriticals = hasUnknownNonCriticals || hasUnknownNonCriticalsChild
135149
if err != nil {
136150
return hasUnknownNonCriticals, err
@@ -153,7 +167,7 @@ func RejectUnknownFields(bz []byte, msg proto.Message, allowUnknownNonCriticals
153167
}
154168
}
155169

156-
hasUnknownNonCriticalsChild, err := RejectUnknownFields(fieldBytes, msg, allowUnknownNonCriticals, resolver)
170+
hasUnknownNonCriticalsChild, err := doRejectUnknownFields(fieldBytes, msg, allowUnknownNonCriticals, resolver, recursionLimit-1)
157171
hasUnknownNonCriticals = hasUnknownNonCriticals || hasUnknownNonCriticalsChild
158172
if err != nil {
159173
return hasUnknownNonCriticals, err

Diff for: x/tx/CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ Since v0.13.0, x/tx follows Cosmos SDK semver: https://github.com/cosmos/cosmos-
3333

3434
## [Unreleased]
3535

36+
## [v0.13.7](https://github.com/cosmos/cosmos-sdk/releases/tag/x/tx/v0.13.7) - 2024-12-16
37+
38+
### Bug Fixes
39+
40+
* Fix [ABS-0043](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-8wcc-m6j2-qxvm) Limit recursion depth for unknown field detection
41+
3642
## [v0.13.6](https://github.com/cosmos/cosmos-sdk/releases/tag/x/tx/v0.13.6) - 2024-12-12
3743

3844
### Bug Fixes

Diff for: x/tx/decode/unknown.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,23 @@ func RejectUnknownFieldsStrict(bz []byte, msg protoreflect.MessageDescriptor, re
3333
// This function traverses inside of messages nested via google.protobuf.Any. It does not do any deserialization of the proto.Message.
3434
// An AnyResolver must be provided for traversing inside google.protobuf.Any's.
3535
func RejectUnknownFields(bz []byte, desc protoreflect.MessageDescriptor, allowUnknownNonCriticals bool, resolver protodesc.Resolver) (hasUnknownNonCriticals bool, err error) {
36+
// recursion limit with same default as https://github.com/protocolbuffers/protobuf-go/blob/v1.35.2/encoding/protowire/wire.go#L28
37+
return doRejectUnknownFields(bz, desc, allowUnknownNonCriticals, resolver, 10_000)
38+
}
39+
40+
func doRejectUnknownFields(
41+
bz []byte,
42+
desc protoreflect.MessageDescriptor,
43+
allowUnknownNonCriticals bool,
44+
resolver protodesc.Resolver,
45+
recursionLimit int,
46+
) (hasUnknownNonCriticals bool, err error) {
3647
if len(bz) == 0 {
3748
return hasUnknownNonCriticals, nil
3849
}
50+
if recursionLimit == 0 {
51+
return false, errors.New("recursion limit reached")
52+
}
3953

4054
fields := desc.Fields()
4155

@@ -100,7 +114,7 @@ func RejectUnknownFields(bz []byte, desc protoreflect.MessageDescriptor, allowUn
100114

101115
if fieldMessage.FullName() == anyFullName {
102116
// Firstly typecheck types.Any to ensure nothing snuck in.
103-
hasUnknownNonCriticalsChild, err := RejectUnknownFields(fieldBytes, anyDesc, allowUnknownNonCriticals, resolver)
117+
hasUnknownNonCriticalsChild, err := doRejectUnknownFields(fieldBytes, anyDesc, allowUnknownNonCriticals, resolver, recursionLimit-1)
104118
hasUnknownNonCriticals = hasUnknownNonCriticals || hasUnknownNonCriticalsChild
105119
if err != nil {
106120
return hasUnknownNonCriticals, err
@@ -120,7 +134,7 @@ func RejectUnknownFields(bz []byte, desc protoreflect.MessageDescriptor, allowUn
120134
fieldBytes = a.Value
121135
}
122136

123-
hasUnknownNonCriticalsChild, err := RejectUnknownFields(fieldBytes, fieldMessage, allowUnknownNonCriticals, resolver)
137+
hasUnknownNonCriticalsChild, err := doRejectUnknownFields(fieldBytes, fieldMessage, allowUnknownNonCriticals, resolver, recursionLimit-1)
124138
hasUnknownNonCriticals = hasUnknownNonCriticals || hasUnknownNonCriticalsChild
125139
if err != nil {
126140
return hasUnknownNonCriticals, err

0 commit comments

Comments
 (0)