Skip to content

BSON and Protobuf bindings read the entire request body without a size limit #4759

Description

@GG-Feng

Description

Summary

The BSON and Protobuf binders read the entire request body into memory before parsing it.

If an application uses these binders without an application-level or proxy-level body size limit, a large request body can force large server-side memory allocations and cause resource exhaustion.

Affected Code

File: binding/bson.go

func (b bsonBinding) Bind(req *http.Request, obj any) error {
    buf, err := io.ReadAll(req.Body)
    if err == nil {
        err = b.BindBody(buf, obj)
    }
    return err
}

File: binding/protobuf.go

func (b protobufBinding) Bind(req *http.Request, obj any) error {
    buf, err := io.ReadAll(req.Body)
    if err != nil {
        return err
    }
    return b.BindBody(buf, obj)
}

Both paths call io.ReadAll(req.Body) without applying a maximum size.

Reproduction

I reproduced the behavior against a minimal Gin server that directly calls the real binding.BSON.Bind path.

A small request body only caused a small allocation:

binding.BSON.Bind err=invalid document length
HeapAlloc=0.7 MiB  TotalAlloc=0.7 MiB  (delta vs baseline = +0.1 MiB)

A 200 MB request body caused the request body to be fully read into memory before parsing failed:

binding.BSON.Bind err=invalid document length
HeapAlloc=516.6 MiB  TotalAlloc=516.8 MiB  (delta vs baseline = +516.2 MiB)

The parse error happens after the full body has already been read, so the memory consumption is triggered even when the body is invalid.

The Protobuf binder uses the same io.ReadAll(req.Body) pattern.

Expected Behavior

Binders should avoid unbounded request body reads, or Gin should provide a clear built-in way to enforce a maximum body size for these binders.

Actual Behavior

The BSON and Protobuf binders read the full request body into memory without a size limit.

Impact

A client can send a very large request body to an endpoint using these binders and force large memory allocations. This may lead to resource exhaustion or service degradation if no external request size limit is configured.

Suggested Fix

  • Add a configurable maximum body size for binders that need to buffer the full request body.
  • Use a bounded reader before calling io.ReadAll.
  • Document the default behavior clearly so applications using BSON or Protobuf binding know they must enforce request size limits.

Gin Version

34dac20

Can you reproduce the bug?

Yes

Source Code

package main

import (
	"fmt"
	"net/http"
	"runtime"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
)

func main() {
	r := gin.Default()

	r.POST("/bson", func(c *gin.Context) {
		var before, after runtime.MemStats
		runtime.ReadMemStats(&before)

		var obj any
		err := binding.BSON.Bind(c.Request, &obj)

		runtime.ReadMemStats(&after)

		c.String(http.StatusOK,
			"binding.BSON.Bind err=%v\nHeapAlloc=%.1f MiB  TotalAlloc=%.1f MiB\n",
			err,
			float64(after.HeapAlloc)/1024/1024,
			float64(after.TotalAlloc)/1024/1024,
		)
	})

	if err := r.Run(":8080"); err != nil {
		panic(err)
	}
}

Run the server, then send a large request body:

go run .
head -c 209715200 /dev/zero | curl -X POST http://localhost:8080/bson --data-binary @-

Observed output from my reproduction:

binding.BSON.Bind err=invalid document length
HeapAlloc=516.6 MiB  TotalAlloc=516.8 MiB

The BSON parse error occurs after the full request body has already been read into memory.

Go Version

go1.26.5

Operating System

Docker container based on golang:1.26

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/bugFound something you weren't expecting? Report it here!

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions