Skip to content

🐛 bug: Fix nil pointer dereference in context methods when accessed after release#4062

Open
Copilot wants to merge 10 commits intomainfrom
copilot/fix-nil-pointer-dereference
Open

🐛 bug: Fix nil pointer dereference in context methods when accessed after release#4062
Copilot wants to merge 10 commits intomainfrom
copilot/fix-nil-pointer-dereference

Conversation

Copy link
Contributor

Copilot AI commented Feb 7, 2026

Description

When fiber.Ctx is passed as context.Context to libraries that spawn goroutines (e.g., minio.GetObject), context-related methods panic with nil pointer dereference. This occurs because c.fasthttp is set to nil when the context is released back to the pool after the request handler completes (via defer app.ReleaseCtx(ctx) in router.go), but goroutines may still call these methods.

The root cause is a lifecycle mismatch: SendStream sets up streaming but doesn't block, so the handler returns immediately, triggering the deferred context release. Fasthttp then reads from the stream after the context has been released and c.fasthttp is nil.

// Previously panicked when context was released
app.Get("/download", func(c fiber.Ctx) error {
    obj, _ := minioClient.GetObject(c, "bucket", "file", minio.GetObjectOptions{})
    return c.SendStream(obj, size)  // Panics in background goroutine
})

Note: While this fix prevents panics through defensive nil checks, users should call c.Context() instead of passing c directly to avoid lifecycle issues with goroutines. The existing documentation already recommends this pattern.

Alternative: Users can also pass c.RequestCtx() which returns the underlying *fasthttp.RequestCtx that persists during streaming, though c.Context() is the recommended approach for general use.

Changes introduced

  • Bug Fix: Added nil checks to all context-related methods that access c.fasthttp:
    • Value() - Returns nil when c.fasthttp is nil (prevents panic, maintains context.Context contract)
    • Context() - Returns context.Background() when c.fasthttp is nil
    • SetContext() - Becomes no-op when c.fasthttp is nil
    • Request() - Returns nil when c.fasthttp is nil
    • Response() - Returns nil when c.fasthttp is nil
  • Code Documentation: Updated godoc comments for Request(), Response(), and Value() methods to explicitly document nil return behavior when context has been released
  • API Documentation: Enhanced documentation in /docs/api/ctx.md for:
    • Request() - Added info box documenting nil return when context is released
    • Response() - Added info box documenting nil return when context is released
    • SendStream() - Documented asynchronous behavior, automatic stream closing, context lifecycle warnings, and added practical file streaming example
  • Tests: Added comprehensive test coverage with race-safe synchronization:
    • Test_Ctx_Value_AfterRelease - Verifies Value() doesn't panic after release
    • Test_Ctx_Value_InGoroutine - Tests goroutine usage with deterministic synchronization (no sleeps) to avoid race conditions
    • Test_Ctx_Request_Response_AfterRelease - Verifies Request()/Response() return nil safely
    • Updated Test_Ctx_Context_AfterHandlerPanics to expect defensive behavior instead of panics

Modified Files:

  • ctx.go: Added nil checks to 5 methods accessing c.fasthttp and updated godoc comments to clarify nil return behavior

  • ctx_test.go: Added 3 new tests and updated 1 existing test to verify defensive behavior with race-safe, deterministic synchronization

  • docs/api/ctx.md: Enhanced documentation for Request(), Response(), and SendStream() methods with nil behavior, async behavior, stream closing, and lifecycle warnings

  • Benchmarks: No performance impact - nil checks are single comparisons in non-hot paths

  • Documentation Update: Updated godoc comments for Request(), Response(), and Value() methods. Enhanced API documentation in /docs/api/ctx.md for Request(), Response(), and SendStream() methods to document nil return behavior, async behavior, automatic stream closing, and proper context usage patterns with goroutines. Context guide already documented c.Context() recommendation for long-running operations.

  • Changelog/What's New: Fixed nil pointer dereference in context methods (Value(), Context(), SetContext(), Request(), Response()) when accessed after handler completion. Improved documentation for Request(), Response(), and SendStream() to clarify nil return behavior, async streaming behavior, and stream lifecycle. Users should use c.Context() for operations in goroutines to avoid lifecycle issues.

  • Migration Guide: No migration needed - transparent fix that provides defensive behavior instead of panics. However, users passing c directly to libraries spawning goroutines should migrate to c.Context() for proper lifecycle management. Documentation now clearly explains nil return behavior for Request/Response methods, streaming behavior, and context lifecycle.

  • API Alignment with Express: N/A - Go-specific context.Context interface compliance

  • API Longevity: Maintains backward compatibility, provides safer behavior for goroutine usage. The nil checks are defensive measures; proper usage is documented. Documentation clarifies expected behavior for edge cases, async operations, and nil return scenarios.

  • Examples: See issue reproduction code. Recommended pattern: obj, _ := minioClient.GetObject(c.Context(), "bucket", "file", opts). Added file streaming example in SendStream documentation demonstrating proper usage.

Type of change

  • Code consistency (non-breaking change which improves code reliability and robustness)
  • Documentation update (changes to documentation)

Checklist

  • 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, including with -race flag.
  • 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.
Original prompt

This section details on the original issue you should resolve

<issue_title>🐛 [Bug]: nil pointer dereference for (fiber.Ctx).Value</issue_title>
<issue_description>### Bug Description

When sending streaming response with object that was created from minio.GetObject with fiber.Ctx context it tries to access ctx.Value but is hit with nil pointer dereference.

How to Reproduce

Steps to reproduce the behavior:

  1. Use minio.GetObject to get io.ReadCloser implementation while using fiber.Ctx as context.Context
  2. Send it in response as body stream
  3. Observe error
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x10538ec84]

goroutine 210 [running]:
github.com/valyala/fasthttp.(*RequestCtx).UserValue(...)
        /Users/vovchara/go/pkg/mod/github.com/valyala/fasthttp@v1.69.0/server.go:773
github.com/gofiber/fiber/v3.(*DefaultCtx).Value(0x1400004bc88?, {0x1065adb00?, 0x107199b20?})
        /Users/vovchara/go/pkg/mod/github.com/gofiber/fiber/v3@v3.0.0/ctx.go:595 +0x24
context.value({0x107ab7d48?, 0x140006ae008?}, {0x1065adb00, 0x107199b20})
        /opt/homebrew/Cellar/go/1.25.7_1/libexec/src/context/context.go:803 +0x230
context.(*cancelCtx).Value(0x140000242c8?, {0x1065adb00?, 0x107199b20?})
        /opt/homebrew/Cellar/go/1.25.7_1/libexec/src/context/context.go:445 +0x6c
net/http/httptrace.ContextClientTrace(...)
        /opt/homebrew/Cellar/go/1.25.7_1/libexec/src/net/http/httptrace/trace.go:25
net/http.(*Transport).roundTrip(0x140000be000, 0x14000334a00)
        /opt/homebrew/Cellar/go/1.25.7_1/libexec/src/net/http/transport.go:585 +0xa4
net/http.(*Transport).RoundTrip(0x14000024498?, 0x10676bd60?)
        /opt/homebrew/Cellar/go/1.25.7_1/libexec/src/net/http/roundtrip.go:33 +0x20
net/http.send(0x14000334a00, {0x10676bd60, 0x140000be000}, {0x1051a24e8?, 0x8?, 0x0?})
        /opt/homebrew/Cellar/go/1.25.7_1/libexec/src/net/http/client.go:259 +0x478
net/http.(*Client).send(0x14000386900, 0x14000334a00, {0x14000024718?, 0x104f199f0?, 0x0?})
        /opt/homebrew/Cellar/go/1.25.7_1/libexec/src/net/http/client.go:180 +0x88
net/http.(*Client).do(0x14000386900, 0x14000334a00)
        /opt/homebrew/Cellar/go/1.25.7_1/libexec/src/net/http/client.go:729 +0x710
net/http.(*Client).Do(...)
        /opt/homebrew/Cellar/go/1.25.7_1/libexec/src/net/http/client.go:587
github.com/minio/minio-go/v7.(*Client).do(0x140004da1e0, 0x14000334a00)
        /Users/vovchara/go/pkg/mod/github.com/minio/minio-go/v7@v7.0.98/api.go:606 +0x64
github.com/minio/minio-go/v7.(*Client).executeMethod-range1(0x148?)
        /Users/vovchara/go/pkg/mod/github.com/minio/minio-go/v7@v7.0.98/api.go:717 +0x124
github.com/minio/minio-go/v7.(*Client).executeMethod.(*Client).newRetryTimer.func3(...)
        /Users/vovchara/go/pkg/mod/github.com/minio/minio-go/v7@v7.0.98/retry.go:84
github.com/minio/minio-go/v7.(*Client).executeMethod(0x140004da1e0, {0x106776570, 0x1400046a050}, {0x105bde51d, 0x3}, {0x0, {0x140003920dd, 0x19}, {0x1400039229a, 0x26}, ...})
        /Users/vovchara/go/pkg/mod/github.com/minio/minio-go/v7@v7.0.98/api.go:691 +0x5dc
github.com/minio/minio-go/v7.(*Client).getObject(_, {_, _}, {_, _}, {_, _}, {0x14000398690, 0x0, {0x0, ...}, ...})
        /Users/vovchara/go/pkg/mod/github.com/minio/minio-go/v7@v7.0.98/api-get-object.go:675 +0x148
github.com/minio/minio-go/v7.(*Client).GetObject.func1()
        /Users/vovchara/go/pkg/mod/github.com/minio/minio-go/v7@v7.0.98/api-get-object.go:204 +0x2a0
created by github.com/minio/minio-go/v7.(*Client).GetObject in goroutine 168
        /Users/vovchara/go/pkg/mod/github.com/minio/minio-go/v7@v7.0.98/api-get-object.go:70 +0x270
Process Exit with Code: 2

Expected Behavior

Body can be streamed in response like it is in fiber v2

Fiber Version

v3.0.0

Code Snippet (optional)

package main

import (
	"context"
	"log"
	"strings"

	"github.com/gofiber/fiber/v3"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
)

func main() {
	minioClient, err := minio.New("localhost:4566", &minio.Options{
		Creds:  credentials.NewStaticV4("minioadmin", "minioadmin", ""),
		Secure: false,
	})
	if err != nil {
		log.Fatal(err)
	}

	app := fiber.New()
	minioClient.MakeBucket(context.Background(), "mybucket", minio.MakeBucketOptions{})
	minioClient.PutObject(context.Background(), "mybucket", "myfile.txt", strings.NewReader("hello"), 5, minio.PutObjectOptions{})

	app.Get("/broken", func(c fiber.Ctx) error {
		obj, err := minioClient.GetObject(c, "mybucket", "myfile.txt", minio.GetObjectOptions{})
		if err != nil {
			return err
		}
		stat, err := obj.Stat()
		if err != nil {
			return err
		}
		c.Response().StreamBody = true
		return c.SendStream(obj, int(stat.Size))
	})

	log.Fatal(app.Listen(":3000"))
}

Checklist:


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: ReneWerner87 <7063188+ReneWerner87@users.noreply.github.com>
@ReneWerner87 ReneWerner87 added this to v3 Feb 7, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone Feb 7, 2026
@gaby
Copy link
Member

gaby commented Feb 7, 2026

@ReneWerner87 the check is nice to merge, but the problem in 4061 is related to context usage i think.

Copilot AI and others added 3 commits February 7, 2026 21:06
Co-authored-by: ReneWerner87 <7063188+ReneWerner87@users.noreply.github.com>
Co-authored-by: ReneWerner87 <7063188+ReneWerner87@users.noreply.github.com>
Co-authored-by: ReneWerner87 <7063188+ReneWerner87@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix nil pointer dereference for fiber.Ctx.Value 🐛 bug: Fix nil pointer dereference in ctx.Value when used in goroutines Feb 7, 2026
Copilot AI requested a review from ReneWerner87 February 7, 2026 21:10
@ReneWerner87
Copy link
Member

ok, lets wait for the comment of the bug author

@gaby
Copy link
Member

gaby commented Feb 7, 2026

@copilot When user passes c to GetObject the issue happens. But if they pass c.RequestCtx() it doesnt.

Copy link
Contributor Author

Copilot AI commented Feb 7, 2026

@copilot When user passes c to GetObject the issue happens. But if they pass c.RequestCtx() it doesnt.

You're correct - c.RequestCtx() returns *fasthttp.RequestCtx which doesn't implement context.Context, so minio wouldn't use it the same way.

The proper recommendation should be for users to call c.Context() instead of passing c directly. The c.Context() method returns a proper context.Context that isn't affected by the fiber context lifecycle.

However, the nil check in Value() is still valuable because:

  1. It prevents panics when fiber.Ctx is used as context.Context (which is a valid use case since it implements the interface)
  2. It follows the context.Context contract where Value() should safely return nil for missing keys
  3. It maintains backward compatibility for existing code that may rely on this behavior

Would you prefer we also add documentation recommending c.Context() for long-running operations?

Copilot AI requested a review from gaby February 7, 2026 21:37
@gaby
Copy link
Member

gaby commented Feb 7, 2026

@copilot That doesnt answer my question. The fasthttp requestctx also implemente Value() here https://github.com/valyala/fasthttp/blob/master/server.go#L2868

  • fiber context implements Value which calls fasthttp value.
  • fasthttp context implements value.
  • calling it via c, results in a panic.
  • passing c.requestctx doesnt panic.

Copy link
Contributor Author

Copilot AI commented Feb 7, 2026

@copilot That doesnt answer my question. The fasthttp requestctx also implemente Value() here https://github.com/valyala/fasthttp/blob/master/server.go#L2868

...

You're absolutely right, and I apologize for the confusion. Here's what's happening:

When you pass c.RequestCtx(), you're passing the actual *fasthttp.RequestCtx pointer. This object remains valid in memory even after the fiber handler completes.

When you pass c (fiber.Ctx), you're passing the fiber wrapper. When the handler completes, fiber calls release() which sets c.fasthttp = nil (ctx.go:672). So when minio's goroutine later calls c.Value(), it tries to access c.fasthttp.UserValue() but c.fasthttp is nil → panic.

The nil check prevents the panic, but you're correct that the root cause is the lifecycle difference:

  • c.RequestCtx() returns a direct pointer to the underlying fasthttp context that persists
  • c is a pooled wrapper that gets reset and its internal fasthttp field is nulled

For minio specifically, c.RequestCtx() works because the underlying fasthttp context survives. The fix still prevents panics for code that passes c directly (which is valid since it implements context.Context).

@gaby
Copy link
Member

gaby commented Feb 7, 2026

@copilot Make sure we check for nil in any place calling c.fasthttp to avoid this issue further on.

Co-authored-by: gaby <835733+gaby@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 7, 2026

@copilot Make sure we check for nil in any place calling c.fasthttp to avoid this issue further on.

Added nil checks to all context-related methods that access c.fasthttp:

  • Context() - returns context.Background() when nil
  • SetContext() - no-op when nil
  • Request() - returns nil when fasthttp is nil
  • Response() - returns nil when fasthttp is nil
  • Value() - already had nil check

These are the methods most likely to be called from goroutines or after handler completion. Updated tests to verify defensive behavior instead of expecting panics.

Commit: 1fcab44

@gaby gaby marked this pull request as ready for review February 8, 2026 00:57
@gaby gaby requested a review from a team as a code owner February 8, 2026 00:57
@gaby gaby requested review from Copilot, efectn and sixcolors February 8, 2026 00:57
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

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens fiber.Ctx’s context.Context-related methods against use-after-release by adding defensive nil checks when the underlying *fasthttp.RequestCtx has been cleared during ReleaseCtx, preventing nil-pointer panics in goroutines (e.g., streaming/object downloads).

Changes:

  • Add defensive nil checks in DefaultCtx methods that access c.fasthttp (Context, SetContext, Value, Request, Response).
  • Add/adjust unit tests to verify post-release behavior does not panic, including a goroutine scenario.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
ctx.go Adds c.fasthttp == nil guards to prevent panics after ReleaseCtx.
ctx_test.go Adds tests validating Value(), Context(), Request()/Response() behavior after release and from a goroutine.

@gaby
Copy link
Member

gaby commented Feb 8, 2026

@copilot apply changes based on the comments in this thread

Co-authored-by: gaby <835733+gaby@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 8, 2026

@copilot apply changes based on the comments in this thread

Applied all changes from the code review:

  1. Updated Request() doc comment to mention nil return after release
  2. Updated Response() doc comment to mention nil return after release
  3. Updated Value() doc comment to clarify nil return behavior when context is released
  4. Removed fixed time.Sleep(50ms) from test - now relies on deterministic completion after app.Test() returns (since handler and deferred ReleaseCtx complete before return)

All tests pass including with -race flag.

Commit: 032385e

Copilot AI requested a review from gaby February 8, 2026 01:06
@gaby
Copy link
Member

gaby commented Feb 8, 2026

@copilot we do not make it clear in the documentation that c.SendStream() closes the provided stream (if possible). We should also document the async behavior of that function from fasthttp.

Let's document this behavior under docs/ctx.md

Co-authored-by: gaby <835733+gaby@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 8, 2026

@copilot we do not make it clear in the documentation that c.SendStream() closes the provided stream (if possible). We should also document the async behavior of that function from fasthttp.

Let's ...

Added comprehensive documentation to docs/api/ctx.md for SendStream:

  1. Async behavior: Documented that SendStream operates asynchronously - handler returns immediately but streaming happens after handler completes (handled by fasthttp)

  2. Stream closing: Documented that streams implementing io.Closer are automatically closed by fasthttp after response completes or on error

  3. Context lifecycle warning: Added caution about passing fiber.Ctx to goroutines with recommended approaches (c.Context() or c.RequestCtx())

  4. Examples: Added file streaming example demonstrating typical usage

Markdown linting passes.

Commit: a86af2f

@gaby
Copy link
Member

gaby commented Feb 8, 2026

@copilot You didnt update the docs for Request() and Response() for nil

Co-authored-by: gaby <835733+gaby@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 8, 2026

@copilot You didnt update the docs for Request() and Response() for nil

Updated API documentation in docs/api/ctx.md for both methods:

  1. Request(): Added info box documenting that it returns nil if the context has been released
  2. Response(): Added info box documenting that it returns nil if the context has been released

Both now clearly document the nil return behavior when context is released after handler completion.

Markdown linting passes.

Commit: b64b1c4

@gaby
Copy link
Member

gaby commented Feb 8, 2026

@copilot Document on the code... Look at this thread #4062 (review) the nil return has to be documented at the code and docs level

Copy link
Contributor Author

Copilot AI commented Feb 8, 2026

@copilot Document on the code... Look at this thread #4062 (review) the nil return has to be documented at the code and docs level

Both levels are already documented:

Code level (ctx.go godoc comments):

  • Line 176: Request() - "Returns nil if the context has been released."
  • Line 187: Response() - "Returns nil if the context has been released."

Docs level (docs/api/ctx.md):

  • Lines 485-487: Request() - Info box documenting nil return when released
  • Lines 526-528: Response() - Info box documenting nil return when released

These were added in commits 032385e (godoc) and b64b1c4 (docs). Both levels are complete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

🐛 [Bug]: nil pointer dereference for (fiber.Ctx).Value

3 participants