Skip to content

sandbox: env_intern_name pool is uncharged and permanent across sandbox_run boundaries (cross-run accumulation) #964

Description

@InauguralPhysicist

Found by iLambdaAi's grader-hardening review (round 14, 2026-08-17), executed A/B proof.

dict_set interns every key via env_intern_name into the process-global g_env_name_interns table, freed only at thread-detach/state-destroy — never between sandbox_runs — and sandbox_charge never sees it. Within one run the key-string extraction is bounded by the charged strbuf chokepoint, but the interned copy is permanent: a long-lived host running many sandbox_runs over untrusted input (the grade() rejection-sampling farm) accumulates unbounded, unreclaimed host memory while every individual run reports ok:1.

Measured: 20,000 runs each decoding an 8-key object with per-run-distinct keys → peak RSS 27.5MB; identical file with keys reused across runs → 7.2MB. Delta ≈ 125 bytes per distinct key, linear and unbounded in run count. On a 4GB farm box, thousands of generations reach the uncatchable x_oom abort with clean grades reported throughout.

Fix directions considered (none trivial, hence an issue rather than a commit on the #963 train):

  • charge interned bytes under an armed sandbox — but the per-run budget resets while the allocation persists, so the accounting would need to be process-global/persistent;
  • epoch the intern table at the sandbox boundary and free run-added interns at teardown — unsafe naively, since keys interned during the run can escape in the returned result's dicts;
  • per-dict key ownership (no interning) when g_sandbox_active — cleanest semantically, but changes dict key lifecycle/compare assumptions.

Interim mitigation shipped in iLambdaAi (lib/validate.eigs residual note): farm drivers recycle the grading process every few thousand generations and run under ulimit -v.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions