Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

`middleware.BodyDump`: data race — error response is written into a dump buffer already returned to `sync.Pool`

Đang mở
#3,173 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@rajpreetcodes đang làm issue này rồi.

Từ ngày 14/10/2026.

  • #3174 của @rajpreetcodes — đang mở

Đánh giá

Độ khó
3/5
Thời gian dự kiến
Nửa ngày
Mức phù hợp với người mới
62/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
go
Lĩnh vực
backend

Hướng nghiên cứu

Start in middleware/body_dump.go, where the response writer is installed with c.SetResponse and the dump buffer is returned to bodyDumpBufferPool, then check echo.go around serveHTTP to see when HTTPErrorHandler runs after the chain returns. Add a regression test based on the reproduction in the issue and run it with go test -race. Done means the race is gone, the writer is restored before BodyDump returns, and the error response body is unchanged.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Echo version: github.com/labstack/echo/v5 v5.4.0
Go version: 1.25

Summary

BodyDump installs a response-writer wrapper on the Context via c.SetResponse and never restores it. It also returns its dump buffer to bodyDumpBufferPool when the middleware returns.

Because Echo.serveHTTP invokes the global HTTPErrorHandler after the middleware chain has returned, the error response is written through the still-installed wrapper — landing in a *bytes.Buffer that has already been handed back to the pool.

Root cause

middleware/body_dump.go:

resBuf := bodyDumpBufferPool.Get().(*bytes.Buffer)   // L116
resBuf.Reset()
defer bodyDumpBufferPool.Put(resBuf)                 // L118  <- returned to pool
...
writer := &bodyDumpResponseWriter{...}
c.SetResponse(writer)                                // L134  <- never restored
err := next(c)                                       // L136
config.Handler(c, reqBody, resBuf.Bytes(), err)      // L139
return err                                           // L141  <- deferred Put runs here

echo.go:

err = e.preChain(c)          // L841
...
if err != nil {
    e.HTTPErrorHandler(c, err)   // L845  <- runs after the chain returned
}

So the ordering is: buffer returned to pool → error handler writes JSON → limitedWriter.Write (L193) → dumpBuf.Write on the pooled buffer.

Reproduction
package middleware_test

import (
	"bytes"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"

	"github.com/labstack/echo/v5"
	"github.com/labstack/echo/v5/middleware"
)

func TestBodyDumpErrorHandlerWritesToReturnedBuffer(t *testing.T) {
	e := echo.New()
	e.HTTPErrorHandler = func(c *echo.Context, err error) {
		_ = c.JSON(http.StatusOK, map[string]any{"error": err.Error()})
	}
	e.Pre(middleware.BodyDump(func(c *echo.Context, reqBody, rspBody []byte, err error) {}))
	e.POST("/err", func(c *echo.Context) error {
		return echo.NewHTTPError(http.StatusBadRequest, "boom")
	})

	var wg sync.WaitGroup
	for i := 0; i < 16; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < 500; j++ {
				req := httptest.NewRequest(http.MethodPost, "/err", bytes.NewReader(make([]byte, 256)))
				e.ServeHTTP(httptest.NewRecorder(), req)
			}
		}()
	}
	wg.Wait()
}
$ go test -race -run TestBodyDumpErrorHandlerWritesToReturnedBuffer ./middleware/
WARNING: DATA RACE
Write at 0x00c00028e0e0 by goroutine 11:
  bytes.(*Buffer).Write()
  middleware.(*limitedWriter).Write()
      middleware/body_dump.go:193
  middleware.(*bodyDumpResponseWriter).Write()
      middleware/body_dump.go:151
  echo.(*delayedStatusWriter).Write()
      response.go:157
  encoding/json.(*Encoder).Encode()
  echo.(*Context).JSON()
  main.TestBodyDumpErrorHandlerWritesToReturnedBuffer.func1()
  echo.(*Echo).serveHTTP()
      echo.go:845

The conflicting access is another request's reqBuf.Reset() at body_dump.go:94. Reproduced on every run (35–43 races per run with 16 goroutines × 500 requests).

Note this needs e.Pre(...) or e.Use(...) — either works, since HTTPErrorHandler is always called after the chain returns.

Impact
  • Genuine data race on bytes.Buffer internals — undefined behaviour per the Go memory model, flagged by -race.
  • The pool is shared by reqBuf (L93) and resBuf (L116), so the stale write can land in another request's reqBuf. BodyDump rebuilds c.Request().Body from that buffer (L111-113), so a concurrent request can have its request body corrupted before it reaches the handler — not just polluted dump output.
  • Client-visible responses are not affected: limitedWriter.Write (L184) writes to the real writer before mirroring into the dump buffer.
  • Intermittent by nature: sync.Pool may drop the object at GC, so it can go unnoticed in short runs.
Suggested fix

Restore the previous response writer before the middleware returns, so that writes occurring after the chain has unwound do not go through the wrapper:

prev := c.Response()
c.SetResponse(writer)
defer c.SetResponse(prev)

Either defer ordering works — the requirement is only that c.Response() is back to the previous writer by the time BodyDump returns.

Workaround

Wrap BodyDump and restore the writer after next returns:

inner := middleware.BodyDumpWithConfig(cfg)
return func(c *echo.Context) error {
	orig := c.Response()
	err := inner(next)(c)
	c.SetResponse(orig)
	return err
}

Or restore it in the global error handler:

if r, uerr := echo.UnwrapResponse(c.Response()); uerr == nil && r != nil {
	c.SetResponse(r)
}

Both verified to eliminate the race (-race clean, error response body unchanged).

Ngôn ngữ chính
Go
Star
32.8k
Fork
5.1k
Merge trung bình
14 giờ 51 phút
Pull request đã merge (30 ngày)
32

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của labstack/echo

Tất cả issue của labstack/echo

Issue tương tự

Thêm issue về Go

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.