-
Notifications
You must be signed in to change notification settings - Fork 0
Fix unbounded memory growth DoS vectors in ollama and opencodecommon #100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -138,3 +138,22 @@ | |
| t.Fatal("expected explicitly disabled responses to not be supported") | ||
| } | ||
| } | ||
|
|
||
| func TestFetchVersion_OOM_Regression(t *testing.T) { | ||
| srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| w.WriteHeader(200) | ||
| chunk := make([]byte, 1024*1024) | ||
| for i := 0; i < 50; i++ { | ||
| w.Write(chunk) | ||
| } | ||
| })) | ||
| defer srv.Close() | ||
|
|
||
| ctx, cancel := context.WithCancel(context.Background()) | ||
| defer cancel() | ||
|
|
||
| _, err := fetchVersion(ctx, srv.Client(), srv.URL) | ||
| if err == nil { | ||
| t.Fatalf("expected error due to large payload or invalid json, got none") | ||
| } | ||
| } | ||
|
Comment on lines
+142
to
+159
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Test doesn't actually validate the OOM fix — it would pass identically without The handler writes 50MB of zero-valued bytes, which is never valid JSON regardless of how much is read. To actually assert bounded memory, either:
As per path instructions, tests should be "testing observable behavior over implementation details" and provide "meaningful assertions" — the current assertion ( 🤖 Prompt for AI AgentsSource: Path instructions |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -132,7 +132,7 @@ func getJSON(ctx context.Context, client *http.Client, endpoint string, headers | |||||||||||||||||||
| body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) | ||||||||||||||||||||
| return nil, fmt.Errorf("opencodecommon: model discovery HTTP status %d: %s", resp.StatusCode, strings.TrimSpace(string(body))) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| body, err := io.ReadAll(resp.Body) | ||||||||||||||||||||
| body, err := io.ReadAll(io.LimitReader(resp.Body, 10*1024*1024)) | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win Silent truncation at 10 MB on the success path with no distinguishing error. Unlike the non-2xx error path above (line 132, which only embeds a snippet in an error string), this line bounds the actual catalog payload that gets parsed and returned to callers. If a real catalog response ever exceeds 10MB, the body is silently truncated and ♻️ Suggested truncation-aware read- body, err := io.ReadAll(io.LimitReader(resp.Body, 10*1024*1024))
+ const maxCatalogBody = 10 * 1024 * 1024
+ body, err := io.ReadAll(io.LimitReader(resp.Body, maxCatalogBody+1))
if err != nil {
return nil, fmt.Errorf("opencodecommon: model discovery read: %w", err)
}
+ if len(body) > maxCatalogBody {
+ return nil, fmt.Errorf("opencodecommon: model discovery response too large (exceeds %d bytes)", maxCatalogBody)
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||
| return nil, fmt.Errorf("opencodecommon: model discovery read: %w", err) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -115,3 +115,22 @@ | |
| t.Fatal("expected error without fallback") | ||
| } | ||
| } | ||
|
|
||
| func TestGetJSON_OOM_Regression(t *testing.T) { | ||
| srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| w.WriteHeader(200) | ||
| chunk := make([]byte, 1024*1024) | ||
| for i := 0; i < 50; i++ { | ||
| w.Write(chunk) | ||
| } | ||
| })) | ||
| defer srv.Close() | ||
|
|
||
| ctx, cancel := context.WithCancel(context.Background()) | ||
| defer cancel() | ||
|
|
||
| _, err := getJSON(ctx, srv.Client(), srv.URL, nil) | ||
| if err == nil { | ||
| t.Fatalf("expected error due to large payload or invalid json, got none") | ||
| } | ||
| } | ||
|
Comment on lines
+119
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Same weak-assertion issue as the ollama regression test — doesn't verify bounded reads. The 50MB payload here is also all-zero bytes, which fails Consider serving a payload that is valid JSON when fully read (so the pre-fix code would succeed) but becomes invalid once truncated at the boundary — this way the test fails without the fix and passes with it. 🤖 Prompt for AI AgentsSource: Path instructions |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
No truncation detection when the 1 MiB limit is hit.
io.ReadAll(io.LimitReader(resp.Body, 1024*1024))returns no error when the body is truncated at exactly 1 MiB — it looks identical to a legitimately-sized response that ends at that byte count. If a real/api/versionpayload were ever larger (e.g., a proxy injecting extra headers/whitespace), the truncated JSON would fail with a generic parse error rather than a clear "response too large" error, making the failure mode confusing to diagnose. Low risk here since version payloads are tiny, but worth a quick fix for clarity.♻️ Suggested truncation-aware read
📝 Committable suggestion
🤖 Prompt for AI Agents