Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/sandbox/daemon-go/internal/routes/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,18 @@ func (d FsDeps) notifyWrite(path string) {
}
}

// decodeBody caps the request body at maxTransferBytes: without a limit, a
// misbehaving or malicious caller could stream an unbounded body into memory
// and crash the daemon, tearing down the sandbox pod on the next missed
// health probe. The cap matches the daemon's other file-transfer bound.
func decodeBody(r *http.Request, out any) error {
raw, err := io.ReadAll(r.Body)
raw, err := io.ReadAll(io.LimitReader(r.Body, maxTransferBytes+1))
if err != nil {
return fmt.Errorf("Failed to parse body: %s", err.Error())
}
if len(raw) > maxTransferBytes {
return fmt.Errorf("Request body exceeded %d bytes", maxTransferBytes)
}
if err := json.Unmarshal(raw, out); err != nil {
return fmt.Errorf("Failed to parse body: %s", err.Error())
}
Expand Down
28 changes: 28 additions & 0 deletions packages/sandbox/daemon-go/internal/routes/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package routes
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)

Expand All @@ -30,6 +32,32 @@ func seedBlocks(t *testing.T) FsDeps {
return FsDeps{AppRoot: filepath.Dir(repoDir), RepoDir: repoDir}
}

// infiniteReader never hits EOF; it lets a test drive decodeBody's cap
// without pre-allocating the oversized body it's rejecting.
type infiniteReader struct{}

func (infiniteReader) Read(p []byte) (int, error) {
for i := range p {
p[i] = 'a'
}
return len(p), nil
}

// Without a cap, decodeBody's io.ReadAll(r.Body) would buffer an unbounded
// request body into memory and could crash the daemon, tearing down the
// sandbox pod on the next missed health probe.
func TestDecodeBodyRejectsOversizedRequest(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/write", io.NopCloser(infiniteReader{}))
var out map[string]any
err := decodeBody(req, &out)
if err == nil {
t.Fatal("expected decodeBody to reject an oversized body, got nil error")
}
if !strings.Contains(err.Error(), "exceeded") {
t.Fatalf("expected a size-limit error, got: %v", err)
}
}

func readReq(t *testing.T, deps FsDeps, path string) *httptest.ResponseRecorder {
t.Helper()
body, _ := json.Marshal(map[string]any{"path": path, "full": true})
Expand Down
Loading