From ba86281e58c1fe6101b587e58b02a4b78d98f622 Mon Sep 17 00:00:00 2001 From: Victor Fusco <1221933+vfusco@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:14:07 -0300 Subject: [PATCH] fix(inspect): limit request body size --- internal/inspect/inspect.go | 20 +++++++++- internal/inspect/inspect_test.go | 63 +++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/internal/inspect/inspect.go b/internal/inspect/inspect.go index 8c97b8ae2..660b54792 100644 --- a/internal/inspect/inspect.go +++ b/internal/inspect/inspect.go @@ -21,6 +21,12 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) +// maxPayloadSize is the maximum allowed inspect request body size. +// This matches the Cartesi Machine's CMIO RX buffer (2 MiB, defined as log2 size 21 +// in the machine emulator's pma-defines.h). Payloads larger than this are rejected +// by the machine anyway, so there is no reason to read them into memory. +const maxPayloadSize = 1 << 21 // 2 MiB + var ( ErrInvalidMachines = errors.New("machines must not be nil") ErrNoApp = errors.New("no application") @@ -119,12 +125,24 @@ func (inspect *Inspector) ServeHTTP(w http.ResponseWriter, r *http.Request) { dapp = r.PathValue("dapp") if r.Method == "POST" { - payload, err = io.ReadAll(r.Body) + // Limit the request body to the machine's CMIO RX buffer size. + // Payloads larger than this are rejected by the machine, so reading + // them into memory would only waste resources. We read maxPayloadSize+1 + // bytes so we can distinguish "exactly at limit" from "over limit". + limitedReader := io.LimitReader(r.Body, maxPayloadSize+1) + payload, err = io.ReadAll(limitedReader) if err != nil { inspect.Logger.Info("Bad request", "err", err) http.Error(w, err.Error(), http.StatusBadRequest) return } + if int64(len(payload)) > maxPayloadSize { + inspect.Logger.Info("Payload too large", + "size", len(payload), + "limit", maxPayloadSize) + http.Error(w, "Payload too large", http.StatusRequestEntityTooLarge) + return + } } else { inspect.Logger.Info("HTTP method not supported", "application", dapp) http.Error(w, "HTTP method not supported", http.StatusNotFound) diff --git a/internal/inspect/inspect_test.go b/internal/inspect/inspect_test.go index 8b00a1743..386eb64b8 100644 --- a/internal/inspect/inspect_test.go +++ b/internal/inspect/inspect_test.go @@ -197,7 +197,68 @@ func (s *InspectSuite) TestPostMachineNotReady() { s.Equal(http.StatusServiceUnavailable, respByAddr.StatusCode) } -// FIXME: add more tests +func (s *InspectSuite) TestPostMaxPayloadSize() { + inspect, app, _ := s.setup() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + s.startServer(ctx, inspect) + + // A payload exactly at the max size should be accepted. + payload := make([]byte, maxPayloadSize) + _, err := crand.Read(payload) + s.Require().NoError(err) + + resp, err := http.Post( + fmt.Sprintf("http://%s/inspect/%s", s.ServiceAddr, app.IApplicationAddress.Hex()), + "application/octet-stream", + bytes.NewReader(payload)) + s.Require().NoError(err) + defer resp.Body.Close() + s.Equal(http.StatusOK, resp.StatusCode) + + var r InspectResponse + err = json.NewDecoder(resp.Body).Decode(&r) + s.Require().NoError(err) + s.Equal("Accepted", r.Status) + s.Require().Len(r.Reports, 1) +} + +func (s *InspectSuite) TestPostPayloadTooLarge() { + inspect, app, _ := s.setup() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + s.startServer(ctx, inspect) + + // A payload one byte over the max size should be rejected. + payload := make([]byte, maxPayloadSize+1) + _, err := crand.Read(payload) + s.Require().NoError(err) + + resp, err := http.Post( + fmt.Sprintf("http://%s/inspect/%s", s.ServiceAddr, app.IApplicationAddress.Hex()), + "application/octet-stream", + bytes.NewReader(payload)) + s.Require().NoError(err) + defer resp.Body.Close() + s.Equal(http.StatusRequestEntityTooLarge, resp.StatusCode) +} + +func (s *InspectSuite) startServer(ctx context.Context, inspect *Inspector) { + router := http.NewServeMux() + router.Handle("/inspect/{dapp}", inspect) + httpService := services.HttpService{Name: "http", Address: s.ServiceAddr, Handler: router} + + ready := make(chan struct{}, 1) + go func() { + _ = httpService.Start(ctx, ready, service.NewLogger(slog.LevelDebug, true)) + }() + + select { + case <-ready: + case <-time.After(TestTimeout): + s.FailNow("timed out waiting for HttpService to be ready") + } +} func (s *InspectSuite) setup() (*Inspector, *Application, common.Hash) { m := newMockMachine(1)