-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Limit recursion depth for unknown field detection and unpack any #22901
Conversation
📝 WalkthroughWalkthroughThis pull request introduces several key changes across multiple components of the system. The primary focus is on enhancing message processing, adding recursion limits, and updating changelogs. The modifications include new constraints for unpacking protobuf Any messages, improved handling of unknown fields, and comprehensive changelog updates that detail new features, improvements, bug fixes, and breaking changes across different modules. Changes
Sequence DiagramsequenceDiagram
participant Client
participant InterfaceRegistry
participant StatefulUnpacker
participant AnyMessage
Client->>InterfaceRegistry: UnpackAny(message, interface)
InterfaceRegistry->>StatefulUnpacker: Create with initial limits
StatefulUnpacker->>AnyMessage: Check recursion depth
alt Depth within limits
StatefulUnpacker-->>InterfaceRegistry: Unpack message
InterfaceRegistry-->>Client: Return unpacked message
else Depth exceeded
StatefulUnpacker-->>Client: Return recursion limit error
end
Possibly Related PRs
Suggested Reviewers
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK but changelogs should be reverted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Nitpick comments (2)
codec/types/interface_registry.go (1)
18-28
: Improve documentation comments for exported variablesAccording to Go conventions, comments for exported variables should start with the variable name and provide a clear description. Adjusting the comments enhances readability and aligns with the Go style guide.
Apply this diff:
var ( - // MaxUnpackAnySubCalls extension point that defines the maximum number of sub-calls allowed during the unpacking + // MaxUnpackAnySubCalls 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 + // MaxUnpackAnyRecursionDepth defines the maximum allowed recursion depth during protobuf Any // message unpacking. MaxUnpackAnyRecursionDepth = 10 )codec/unknownproto/unknown_fields.go (1)
43-45
: Define recursion limit as a constant for consistencyHardcoding the recursion limit of
10,000
reduces maintainability. Defining it as a constant improves readability and allows for easier adjustments in the future.Apply this diff:
+const defaultRecursionLimit = 10000 func RejectUnknownFields(bz []byte, msg proto.Message, allowUnknownNonCriticals bool, resolver jsonpb.AnyResolver) (hasUnknownNonCriticals bool, err error) { - return doRejectUnknownFields(bz, msg, allowUnknownNonCriticals, resolver, 10_000) + return doRejectUnknownFields(bz, msg, allowUnknownNonCriticals, resolver, defaultRecursionLimit) }
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
CHANGELOG.md
(0 hunks)codec/types/interface_registry.go
(5 hunks)codec/unknownproto/unknown_fields.go
(3 hunks)x/tx/CHANGELOG.md
(1 hunks)x/tx/decode/unknown.go
(3 hunks)
💤 Files with no reviewable changes (1)
- CHANGELOG.md
✅ Files skipped from review due to trivial changes (1)
- x/tx/CHANGELOG.md
🧰 Additional context used
📓 Path-based instructions (3)
codec/unknownproto/unknown_fields.go (1)
Pattern **/*.go
: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.
codec/types/interface_registry.go (1)
Pattern **/*.go
: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.
x/tx/decode/unknown.go (1)
Pattern **/*.go
: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.
// recursion limit with same default as https://github.com/protocolbuffers/protobuf-go/blob/v1.35.2/encoding/protowire/wire.go#L28 | ||
return doRejectUnknownFields(bz, desc, allowUnknownNonCriticals, resolver, 10_000) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Define recursion limit as a constant for maintainability
Currently, the recursion limit of 10,000
is hardcoded within the RejectUnknownFields
function. Defining this value as a constant improves readability and makes it easier to manage or adjust in the future.
Apply this diff to define the recursion limit as a constant:
+const defaultRecursionLimit = 10000
func RejectUnknownFields(bz []byte, desc protoreflect.MessageDescriptor, allowUnknownNonCriticals bool, resolver protodesc.Resolver) (hasUnknownNonCriticals bool, err error) {
- return doRejectUnknownFields(bz, desc, allowUnknownNonCriticals, resolver, 10_000)
+ return doRejectUnknownFields(bz, desc, allowUnknownNonCriticals, resolver, defaultRecursionLimit)
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// recursion limit with same default as https://github.com/protocolbuffers/protobuf-go/blob/v1.35.2/encoding/protowire/wire.go#L28 | |
return doRejectUnknownFields(bz, desc, allowUnknownNonCriticals, resolver, 10_000) | |
} | |
const defaultRecursionLimit = 10000 | |
func RejectUnknownFields(bz []byte, desc protoreflect.MessageDescriptor, allowUnknownNonCriticals bool, resolver protodesc.Resolver) (hasUnknownNonCriticals bool, err error) { | |
// recursion limit with same default as https://github.com/protocolbuffers/protobuf-go/blob/v1.35.2/encoding/protowire/wire.go#L28 | |
return doRejectUnknownFields(bz, desc, allowUnknownNonCriticals, resolver, defaultRecursionLimit) | |
} |
if recursionLimit == 0 { | ||
return false, errors.New("recursion limit reached") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Update recursion limit check to handle non-positive values
The current recursion limit check only handles the case when recursionLimit
equals zero. To prevent potential underflows and enhance robustness, consider updating the condition to check for less than or equal to zero.
Apply this diff:
if len(bz) == 0 {
return hasUnknownNonCriticals, nil
}
-if recursionLimit == 0 {
+if recursionLimit <= 0 {
return false, errors.New("recursion limit reached")
}
Committable suggestion skipped: line range outside the PR's diff.
@@ -131,7 +145,7 @@ | |||
fieldBytes = a.Value | |||
} | |||
|
|||
hasUnknownNonCriticalsChild, err := RejectUnknownFields(fieldBytes, fieldMessage, allowUnknownNonCriticals, resolver) | |||
hasUnknownNonCriticalsChild, err := doRejectUnknownFields(fieldBytes, fieldMessage, allowUnknownNonCriticals, resolver, recursionLimit-1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consistent recursion limit handling in recursive calls
Similar to the previous comment, ensure that all recursive calls to doRejectUnknownFields
properly handle the decrementing of recursionLimit
and prevent potential underflow.
Apply this diff:
hasUnknownNonCriticalsChild, err := doRejectUnknownFields(fieldBytes, fieldMessage, allowUnknownNonCriticals, resolver, recursionLimit-1)
+if err != nil {
+ return hasUnknownNonCriticals, err
+}
Ensure that the recursive function handles recursionLimit <= 0
appropriately, as previously suggested.
Committable suggestion skipped: line range outside the PR's diff.
@@ -111,7 +125,7 @@ | |||
|
|||
if fieldMessage.FullName() == anyFullName { | |||
// Firstly typecheck types.Any to ensure nothing snuck in. | |||
hasUnknownNonCriticalsChild, err := RejectUnknownFields(fieldBytes, anyDesc, allowUnknownNonCriticals, resolver) | |||
hasUnknownNonCriticalsChild, err := doRejectUnknownFields(fieldBytes, anyDesc, allowUnknownNonCriticals, resolver, recursionLimit-1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Ensure decrementing recursion limit does not cause underflow
When recursively calling doRejectUnknownFields
, ensure that decrementing recursionLimit
does not result in negative values, which could bypass the termination condition.
Consider verifying that recursionLimit
is greater than zero before the recursive call:
if fieldMessage.FullName() == anyFullName {
// Firstly typecheck types.Any to ensure nothing snuck in.
- hasUnknownNonCriticalsChild, err := doRejectUnknownFields(fieldBytes, anyDesc, allowUnknownNonCriticals, resolver, recursionLimit-1)
+ if recursionLimit > 0 {
+ hasUnknownNonCriticalsChild, err := doRejectUnknownFields(fieldBytes, anyDesc, allowUnknownNonCriticals, resolver, recursionLimit-1)
+ } else {
+ return false, errors.New("recursion limit reached")
+ }
hasUnknownNonCriticals = hasUnknownNonCriticals || hasUnknownNonCriticalsChild
if err != nil {
return hasUnknownNonCriticals, err
}
Alternatively, ensure the recursion limit check at the beginning of the function handles non-positive values as previously suggested.
Committable suggestion skipped: line range outside the PR's diff.
if r.maxDepth == 0 { | ||
return errors.New("max depth exceeded") | ||
} | ||
if r.maxCalls.count == 0 { | ||
return errors.New("call limit exceeded") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Update limit checks to handle non-positive values
The current checks for maxDepth
and maxCalls.count
only handle zero values. To enhance robustness and prevent potential underflows, consider updating the conditions to check for less than or equal to zero.
Apply this diff:
func (r *statefulUnpacker) UnpackAny(any *Any, iface interface{}) error {
- if r.maxDepth == 0 {
+ if r.maxDepth <= 0 {
return errors.New("max depth exceeded")
}
- if r.maxCalls.count == 0 {
+ if r.maxCalls.count <= 0 {
return errors.New("call limit exceeded")
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if r.maxDepth == 0 { | |
return errors.New("max depth exceeded") | |
} | |
if r.maxCalls.count == 0 { | |
return errors.New("call limit exceeded") | |
} | |
if r.maxDepth <= 0 { | |
return errors.New("max depth exceeded") | |
} | |
if r.maxCalls.count <= 0 { | |
return errors.New("call limit exceeded") | |
} |
@@ -240,6 +290,8 @@ | |||
return nil | |||
} | |||
|
|||
r.maxCalls.count-- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Prevent maxCalls.count
from becoming negative
After decrementing r.maxCalls.count
, ensure it does not become negative, which could lead to incorrect behavior. Consider adding a check after the decrement.
Apply this diff:
r.maxCalls.count--
+if r.maxCalls.count < 0 {
+ return errors.New("call limit exceeded")
+}
Alternatively, since the limit check has been updated to handle non-positive values, this might already be addressed.
Committable suggestion skipped: line range outside the PR's diff.
@@ -125,7 +139,7 @@ func RejectUnknownFields(bz []byte, msg proto.Message, allowUnknownNonCriticals | |||
|
|||
if protoMessageName == ".google.protobuf.Any" { | |||
// Firstly typecheck types.Any to ensure nothing snuck in. | |||
hasUnknownNonCriticalsChild, err := RejectUnknownFields(fieldBytes, (*types.Any)(nil), allowUnknownNonCriticals, resolver) | |||
hasUnknownNonCriticalsChild, err := doRejectUnknownFields(fieldBytes, (*types.Any)(nil), allowUnknownNonCriticals, resolver, recursionLimit-1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Ensure proper handling of recursion limit during recursive calls
When recursively calling doRejectUnknownFields
, ensure that the decrementing of recursionLimit
does not result in negative values, which could bypass termination conditions.
Consider verifying that recursionLimit
is greater than zero before the recursive call or rely on the updated recursion limit check as previously suggested.
if recursionLimit == 0 { | ||
return false, errors.New("recursion limit reached") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Update recursion limit check to handle non-positive values
To prevent potential underflows and enhance robustness, consider updating the recursion limit check from recursionLimit == 0
to recursionLimit <= 0
.
Apply this diff:
if len(bz) == 0 {
return hasUnknownNonCriticals, nil
}
-if recursionLimit == 0 {
+if recursionLimit <= 0 {
return false, errors.New("recursion limit reached")
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if recursionLimit == 0 { | |
return false, errors.New("recursion limit reached") | |
} | |
if len(bz) == 0 { | |
return hasUnknownNonCriticals, nil | |
} | |
if recursionLimit <= 0 { | |
return false, errors.New("recursion limit reached") | |
} |
Description
Limit recursion depth for unknown field detection and unpack any
Author Checklist
All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.
I have...
!
in the type prefix if API or client breaking changeCHANGELOG.md
Reviewers Checklist
All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.
Please see Pull Request Reviewer section in the contributing guide for more information on how to review a pull request.
I have...
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
API Breaking Changes
testutil/network
package and client prompt validations.Chores