Skip to content
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: Respect Immutable config for Body() #3246

Merged
merged 2 commits into from
Dec 13, 2024

Conversation

nickajacks1
Copy link
Member

Description

For request bodies without encoding, the Immutable configuration was not being respected, leading to potential data races.
go run -race no longer produces warnings for the example in the original issue.

Fixes #3236

Type of change

Please delete options that are not relevant.

  • Enhancement (improvement to existing features and functionality)

Checklist

Before you submit your pull request, please make sure you meet these requirements:

  • Followed the inspiration of the Express.js framework for new functionalities, making them similar in usage.
  • Conducted a self-review of the code and provided comments for complex or critical parts.
  • Updated the documentation in the /docs/ directory for Fiber's documentation.
  • Added or updated unit tests to validate the effectiveness of the changes or new features.
  • Ensured that new and existing unit tests pass locally with the changes.
  • Verified that any new dependencies are essential and have been agreed upon by the maintainers/community.
  • Aimed for optimal performance with minimal allocations in the new code.
  • Provided benchmarks for the new code to analyze and improve upon.

Commit formatting

Please use emojis in commit messages for an easy way to identify the purpose or intention of a commit. Check out the emoji cheatsheet here: CONTRIBUTING.md

@nickajacks1 nickajacks1 requested a review from a team as a code owner December 12, 2024 00:25
@nickajacks1 nickajacks1 requested review from gaby, sixcolors, ReneWerner87 and efectn and removed request for a team December 12, 2024 00:25
Copy link
Contributor

coderabbitai bot commented Dec 12, 2024

Walkthrough

The changes primarily involve the Body and BodyRaw methods of the Ctx struct in the Fiber web framework. A new private method, getBody, has been introduced to centralize the logic for retrieving body data. This method checks the Immutable configuration: if enabled, it returns a copy of the body data, ensuring the original remains unaltered. If not set, it returns the raw body data directly. The overall structure and logic of the methods are maintained while enhancing body data safety.

Changes

File Change Summary
ctx.go Introduced getBody method for centralized body data retrieval; modified Body and BodyRaw methods to use getBody for handling Immutable configuration.

Assessment against linked issues

Objective Addressed Explanation
Ensure Immutable configuration works as expected to prevent data races (Issue #3236)

Possibly related PRs

  • 🔥 Feature (v3): Add buffered streaming support #3131: This PR introduces a new method SendStreamWriter in the ctx.go file, which is related to the changes in the Body and BodyRaw methods in the main PR as they both involve modifications to the Ctx struct in the same file, focusing on body data handling.
  • 📚 Doc: Updates to Context documentation #3206: This PR updates the documentation for the Body and BodyRaw methods in the docs/api/ctx.md file, which directly relates to the changes made in the main PR regarding these methods.

Suggested labels

✏️ Feature, v3

Suggested reviewers

  • sixcolors
  • ReneWerner87

🐇 In the meadow, I hop and play,
With body data safe today!
Immutable's charm, oh what a treat,
No more races, my code's complete!
With every byte, I dance with glee,
Fiber's magic, just wait and see! 🌼


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Experiment)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@gaby gaby changed the title 🐛 fix: respect Immutable config for Body() 🐛 fix: Respect Immutable config for Body() Dec 12, 2024
@gaby gaby added v3 v2 and removed v3 labels Dec 12, 2024
@gaby
Copy link
Member

gaby commented Dec 12, 2024

@nickajacks1 This happens in v3 too right? If so can you send a PR for that target branch

ctx.go Outdated Show resolved Hide resolved
@nickajacks1
Copy link
Member Author

@gaby no, v3 properly checks for immutability in the getBody() function. I can update to add getBody. Actually, we should probably just backport that function. Let me make that update.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
ctx.go (2)

Line range hint 348-365: Consider respecting Immutable config in tryDecodeBodyInOrder.

The method modifies the request body during decoding attempts using SetBodyRaw. When Immutable is true, consider creating copies of the body for each decoding attempt to prevent potential data races.

 func (c *Ctx) tryDecodeBodyInOrder(
 	originalBody *[]byte,
 	encodings []string,
 ) ([]byte, uint8, error) {
 	var (
 		err             error
 		body            []byte
 		decodesRealized uint8
 	)
 
 	for index, encoding := range encodings {
 		decodesRealized++
 		switch encoding {
 		case StrGzip:
 			body, err = c.fasthttp.Request.BodyGunzip()
 		case StrBr, StrBrotli:
 			body, err = c.fasthttp.Request.BodyUnbrotli()
 		case StrDeflate:
 			body, err = c.fasthttp.Request.BodyInflate()
 		default:
 			decodesRealized--
 			if len(encodings) == 1 {
 				body = c.fasthttp.Request.Body()
+				if c.app.config.Immutable {
+					body = utils.CopyBytes(body)
+				}
 			}
 			return body, decodesRealized, nil
 		}
 
 		if err != nil {
 			return nil, decodesRealized, err
 		}
 
 		// Only execute body raw update if it has a next iteration to try to decode
 		if index < len(encodings)-1 && decodesRealized > 0 {
 			if index == 0 {
 				tempBody := c.fasthttp.Request.Body()
+				if c.app.config.Immutable {
+					tempBody = utils.CopyBytes(tempBody)
+				}
 				*originalBody = make([]byte, len(tempBody))
 				copy(*originalBody, tempBody)
 			}
 			c.fasthttp.Request.SetBodyRaw(body)
 		}
 	}
 
 	return body, decodesRealized, nil
 }

Potential data leak in context pool reuse

The verification confirms that several fields are not properly reset between reuses:

  • pathBuffer and detectionPathBuffer byte slices are not reset
  • values array is not cleared
  • path, detectionPath, and treePath strings are not reset

While some fields are initialized in AcquireCtx, these specific fields could retain data from previous requests, potentially leading to data leaks or incorrect behavior.

Locations requiring attention:

  • ctx.go:190: ReleaseCtx should be updated to reset:
    • pathBuffer = nil
    • detectionPathBuffer = nil
    • values = [maxParams]string{}
    • path = ""
    • detectionPath = ""
    • treePath = ""
🔗 Analysis chain

Line range hint 89-91: Verify context cleanup in the pool reuse.

The context is reused from a pool, but not all fields are explicitly reset in ReleaseCtx. Consider resetting additional fields like pathBuffer, detectionPathBuffer, and values to prevent potential data leaks or race conditions.

Also applies to: 149-163


The initial results confirm that not all fields are being reset in ReleaseCtx. Let's verify if there's any initialization of these fields elsewhere, particularly in the context acquisition method.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for field declarations in Ctx struct and compare with resets in ReleaseCtx
echo "Fields in Ctx struct:"
ast-grep --pattern 'type Ctx struct {
  $$$
}'

echo -e "\nField resets in ReleaseCtx:"
ast-grep --pattern 'func (app *App) ReleaseCtx(c *Ctx) {
  $$$
}'

Length of output: 1967


Script:

#!/bin/bash
# Search for context acquisition and initialization
echo "Context acquisition and initialization:"
ast-grep --pattern 'func (app *App) AcquireCtx(fctx *fasthttp.RequestCtx) *Ctx {
  $$$
}'

# Also check if there's a New or init method
echo -e "\nNew/Init methods:"
ast-grep --pattern 'func New$_($$$) *Ctx {
  $$$
}'

Length of output: 1201

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8c84b0f and 6e40bd8.

📒 Files selected for processing (1)
  • ctx.go (1 hunks)
🔇 Additional comments (1)
ctx.go (1)

343-347: LGTM! The changes properly respect the Immutable configuration.

The implementation correctly handles the Immutable configuration by creating a copy of the request body when needed, which prevents data races when processing the body in separate goroutines.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
ctx.go (1)

Line range hint 338-341: Consider enhancing the method documentation.

While the implementation is solid, the method documentation could be more explicit about the behavior when Immutable is enabled, particularly regarding memory implications of body copying.

- // Body contains the raw body submitted in a POST request.
- // This method will decompress the body if the 'Content-Encoding' header is provided.
- // It returns the original (or decompressed) body data which is valid only within the handler.
- // Don't store direct references to the returned data.
+ // Body contains the raw body submitted in a POST request.
+ // This method will decompress the body if the 'Content-Encoding' header is provided.
+ // When config.Immutable is true, it returns a copy of the body, which is safe to use
+ // across goroutines. Otherwise, it returns the original body which is only valid
+ // within the handler and should not be stored or used in other goroutines.
+ // 
+ // Note: When Immutable is enabled, each call creates a new copy of the body,
+ // which has memory implications for large payloads.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8c84b0f and 6e40bd8.

📒 Files selected for processing (1)
  • ctx.go (1 hunks)
🔇 Additional comments (1)
ctx.go (1)

343-347: LGTM! The fix correctly implements body immutability.

The implementation now properly respects the Immutable configuration by returning a copy of the body when enabled. This prevents data races when accessing the request body across multiple goroutines.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
ctx.go (1)

343-347: Consider optimizing the copy operation.

While the current implementation is correct, we could optimize it by only copying the body when it's actually going to be stored/used beyond the handler's lifecycle. This could be done by exposing a new method like ImmutableBody() for cases requiring immutability, while keeping Body() fast for read-only operations.

Example implementation:

func (c *Ctx) Body() []byte {
-	body = c.fasthttp.Request.Body()
-	if c.app.config.Immutable {
-		return utils.CopyBytes(body)
-	}
-	return body
+	return c.fasthttp.Request.Body()
+}
+
+// ImmutableBody returns a copy of the body that is safe to use beyond the handler's lifecycle
+func (c *Ctx) ImmutableBody() []byte {
+	return utils.CopyBytes(c.fasthttp.Request.Body())
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8c84b0f and 6e40bd8.

📒 Files selected for processing (1)
  • ctx.go (1 hunks)
🔇 Additional comments (2)
ctx.go (2)

343-347: LGTM! The change correctly handles immutable body data.

The implementation properly respects the Immutable configuration by returning a copy of the body data when enabled, which prevents potential data races when accessing the request body across different goroutines.


343-347: Verify thread safety of the implementation.

Let's confirm that the implementation is truly thread-safe by checking the utils.CopyBytes() implementation and testing concurrent access patterns.

✅ Verification successful

Let me gather more information about the decompression methods and their thread safety.


Based on the gathered information, I can now provide the final response:

Implementation is thread-safe when Immutable is enabled

The implementation is verified to be thread-safe when Immutable config is enabled because:

  • utils.CopyBytes() creates a new byte slice and performs a deep copy of the data
  • The implementation properly handles concurrent access by:
    • Creating a new slice with make([]byte, len(b))
    • Using copy() to duplicate the data
    • Returning the new independent slice

However, when Immutable is false, direct access to the request body is returned which could lead to data races in concurrent scenarios. Consider making immutable behavior the default to prevent potential race conditions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of utils.CopyBytes and its usage

# Check utils.CopyBytes implementation
echo "Checking utils.CopyBytes implementation..."
ast-grep --pattern 'func CopyBytes(b []byte) []byte {
  $$$
}'

# Look for other direct body access patterns that might bypass the immutable check
echo "Checking for direct body access patterns..."
rg "Request.Body()" --type go

Length of output: 820


Script:

#!/bin/bash
# Check implementation of body decompression methods
echo "Checking body decompression implementations..."
rg -A 10 "func \(c \*Ctx\) Body\(\)" --type go

# Check if there are any other places where the body is stored or cached
echo "Checking body storage patterns..."
rg "var body" --type go -A 2

Length of output: 633

@gaby
Copy link
Member

gaby commented Dec 12, 2024

@nickajacks1 While you are at it, add go1.22 and 1.23 to the tests workflow

@nickajacks1
Copy link
Member Author

Sure. This pr or separate?

@gaby
Copy link
Member

gaby commented Dec 12, 2024

Sure. This pr or separate?

Same PR

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
ctx.go (3)

271-271: Update documentation for BodyRaw() to reflect Immutable configuration handling

The BodyRaw() method now calls getBody(), which respects the Immutable setting by returning a copy of the body data when enabled. Consider updating the method's documentation to reflect this behavior change.


343-343: Update documentation for Body() to reflect Immutable configuration handling

Since Body() now uses getBody() when no content encoding is present, it's essential to update the documentation to indicate that the method respects the Immutable setting by returning a copy of the body data when enabled.


2011-2017: Add documentation for the new getBody() method

Consider adding a comment to the getBody() method to explain its purpose and how it centralizes body data retrieval while respecting the Immutable configuration. This will enhance code readability and maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e40bd8 and 06ad889.

⛔ Files ignored due to path filters (1)
  • .github/workflows/test.yml is excluded by !**/*.yml
📒 Files selected for processing (1)
  • ctx.go (4 hunks)
🔇 Additional comments (1)
ctx.go (1)

357-359: Properly handling Immutable configuration after body decoding

Good job ensuring that the Immutable setting is respected even after decoding the body by returning a copy of the body when Immutable is enabled. This prevents potential data races when the body is accessed across different goroutines.

Copy link
Member

@gaby gaby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 LGTM

@ReneWerner87 ReneWerner87 added this to the Next Release milestone Dec 13, 2024
@ReneWerner87 ReneWerner87 merged commit 56ff2de into gofiber:v2 Dec 13, 2024
27 checks passed
@nickajacks1 nickajacks1 deleted the issue-3236 branch December 13, 2024 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants