Summary
PostInit in packages/envd/internal/api/init.go reads the request body with an unbounded io.ReadAll:
// packages/envd/internal/api/init.go ~L141
body, err := io.ReadAll(r.Body) // no MaxBytesReader
defer memguard.WipeBytes(body)
Any process inside the VM that can reach the envd HTTP port can send an arbitrarily large body, allocating heap memory until envd is OOM-killed.
Why it matters
envd is the sandbox control plane — it manages file I/O, process execution, cgroup freezing, NFS mounts, and live-upgrade handover. An OOM kill of envd:
- leaves all user processes running but orphaned (no envd to receive commands)
- prevents graceful sandbox teardown (cleanup callbacks, slot release)
- breaks any in-progress pause/resume sequence, potentially corrupting snapshot state
The /init endpoint is excluded from authExcludedPaths but does accept unauthenticated bodies — the auth check happens after the body is fully read. A guest process (e.g. user code running inside the sandbox) that discovers the envd port can therefore trigger OOM without any credentials.
Secondary issue: memguard.WipeBytes security value is reduced
memguard.WipeBytes(body) is deferred to scrub the access token from heap memory. But if body contains the token and is first replicated into a large allocation (e.g. a 500 MiB body), Go's allocator may have already copied the slice header or the GC may have paged parts to disk before the wipe runs. Capping the body to a size where the entire buffer fits comfortably in memory preserves the intent of the wipe.
Fix
Wrap r.Body with http.MaxBytesReader before reading. The largest legitimate /init payload contains EnvVars (many vars) and CaBundle (multiple PEM certs); 1 MiB is a generous upper bound that no real orchestrator payload will approach:
if r.Body != nil {
// Cap body to 1 MiB. /init carries credentials and CA bundles but no
// bulk data. An unbounded io.ReadAll lets a guest process OOM envd,
// the sandbox control plane, by sending an oversized body.
r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
body, err := io.ReadAll(r.Body)
defer memguard.WipeBytes(body)
if err != nil {
var maxErr *http.MaxBytesError
if errors.As(err, &maxErr) {
logger.Error().Msg("request body exceeds 1 MiB limit")
w.WriteHeader(http.StatusRequestEntityTooLarge)
} else {
logger.Error().Msgf("Failed to read request body: %v", err)
w.WriteHeader(http.StatusBadRequest)
}
return
}
...
No changes needed to imports (errors, io, net/http are all already imported).
Severity
Medium. The envd HTTP port is not externally exposed; exploitation requires code execution inside the VM. However sandboxes are explicitly designed to run untrusted LLM-generated code, so "arbitrary code inside the VM" is the normal threat model, not an unusual escalation.
Summary
PostInitinpackages/envd/internal/api/init.goreads the request body with an unboundedio.ReadAll:Any process inside the VM that can reach the envd HTTP port can send an arbitrarily large body, allocating heap memory until envd is OOM-killed.
Why it matters
envd is the sandbox control plane — it manages file I/O, process execution, cgroup freezing, NFS mounts, and live-upgrade handover. An OOM kill of envd:
The
/initendpoint is excluded fromauthExcludedPathsbut does accept unauthenticated bodies — the auth check happens after the body is fully read. A guest process (e.g. user code running inside the sandbox) that discovers the envd port can therefore trigger OOM without any credentials.Secondary issue:
memguard.WipeBytessecurity value is reducedmemguard.WipeBytes(body)is deferred to scrub the access token from heap memory. But ifbodycontains the token and is first replicated into a large allocation (e.g. a 500 MiB body), Go's allocator may have already copied the slice header or the GC may have paged parts to disk before the wipe runs. Capping the body to a size where the entire buffer fits comfortably in memory preserves the intent of the wipe.Fix
Wrap
r.Bodywithhttp.MaxBytesReaderbefore reading. The largest legitimate/initpayload containsEnvVars(many vars) andCaBundle(multiple PEM certs); 1 MiB is a generous upper bound that no real orchestrator payload will approach:No changes needed to imports (
errors,io,net/httpare all already imported).Severity
Medium. The envd HTTP port is not externally exposed; exploitation requires code execution inside the VM. However sandboxes are explicitly designed to run untrusted LLM-generated code, so "arbitrary code inside the VM" is the normal threat model, not an unusual escalation.