GHSA-wwqq-x6w4-frm2 Gitea: Denial of Service via Unbounded io.ReadAll in NPM Package Tag Endpoint
SummaryAn unbounded `io.ReadAll(ctx.Req.Body)` call in the NPM package tag API endpoint allows any authenticated user to crash the Gitea server by sending a single large HTTP request. The request body is read entirely into memory with no size limit, causing an Out-of-Memory (OOM) kill. With concurrent requests, the attack produces a persistent denial of service that survives automatic restarts.DetailsThe [`AddPackageTag`](https://github.com/go-gitea/gitea/blob/a12f9807933bd463368c6111dbc283d8a65f20f7/routers/api/packages/npm/npm.go#L336) function reads the entire HTTP request body into memory using `io.ReadAll()` with no size validation:This route is registered at [`routers/api/packages/api.go:433`](https://github.com/go-gitea/gitea/blob/a12f9807933bd463368c6111dbc283d8a65f20f7/routers/api/packages/api.go#L433):**Why this causes OOM and not just a slow request:**In Go, `io.ReadAll()` reads into a `[]byte` that grows dynamically. When the incoming data exceeds available memory, the Go runtime attempts to allocate a larger backing array. This allocation fails, triggering an unrecoverable `runtime.throw("out of memory")` that kills the entire process, not just the goroutine handling the request.**No server-side size limits apply to this endpoint:**Gitea has per-type size limits (e.g., `LIMIT_SIZE_NPM`) defined in [`modules/setting/packages.go`](https://github.com/go-gitea/gitea/blob/a12f9807933bd463368c6111dbc283d8a65f20f7/modules/setting/packages.go#L35), but these are only enforced during `UploadPackage`, not in `AddPackageTag`. The [`mustBytes()`](https://github.com/go-gitea/gitea/blob/a12f9807933bd463368c6111dbc283d8a65f20f7/modules/setting/packages.go#L96-L108) function defaults all limits to `-1` (unlimited) when not explicitly configured:Even if an admin sets `LIMIT_SIZE_NPM`, it would not protect this endpoint. `AddPackageTag` never checks any size limit before calling `io.ReadAll()`.The Gitea HTTP server has no global request body size limit. The [`HashedBuffer`](https://github.com/go-gitea/gitea/blob/a12f9807933bd463368c6111dbc283d8a65f20f7/modules/packages/hashed_buffer.go#L20-L33) used for package uploads (which does have a 32MB memory buffer before spilling to disk) is not used for this endpoint. `AddPackageTag` reads the body directly via `io.ReadAll()`, bypassing all buffer protections:**Access requirements:**The route requires `reqPackageAccess(perm.AccessModeWrite)`Any user has write access to their own package namespace ([`services/context/package.go:155-157`](https://github.com/go-gitea/gitea/blob/a12f9807933bd463368c6111dbc283d8a65f20f7/services/context/package.go#L155-L157)):No NPM package needs to exist. The OOM occurs at line 336 before the [package lookup at line 343](https://github.com/go-gitea/gitea/blob/a12f9807933bd463368c6111dbc283d8a65f20f7/routers/api/packages/npm/npm.go#L343):PoC**Tested Environment:**Gitea instance (tested on v1.26.2 Docker, confirmed in source up to v1.27.0-dev)**Prerequisites: Set up test environment****Step 1: Single request OOM crash****Step 2: Verify server crash****Step 3: Persistent DoS via concurrent requests (survives restart policies)**One 400MB upload triggers OOM killhttps://github.com/user-attachments/assets/a7ba4566-56d5-41ba-ad9f-7e23045fa0f6Crash loop after OOM with Docker restart policyhttps://github.com/user-attachments/assets/9211649f-e10c-4b78-a1e5-223ab90d04a7**Observed result on Gitea 1.26.2:**Server logs: `Received signal 15; terminating.`Container status: `Exited (0)`Server remains down until manual restartWith `restart: always`, server restarts but can be immediately re-killedImpact**Who is impacted:**All Gitea instances with the package registry enabled (enabled by default)Any authenticated user can crash the server (No admin privileges required)With self-registration enabled (default), an unauthenticated attacker can register an account and immediately crash the serverAll users of the Gitea instance lose access to repositories, CI/CD, issues, and all hosted services**Attack characteristics:****Single request** is sufficient to crash the server**No special payload**: raw zeros work (no compression tricks needed)**Persistent** multiple requests can re-kill the server even after auto-restart**Minimal bandwidth**: attacker sends ~80% of the server's available memory in a single request to crash it (e.g., ~400MB for a 512MB instance, ~1.6GB for a 2GB instance)
CVSS v3.1 score: 6.5