Skip to content

kilnd --memory cap does not bound table memory: oversized table min / table.grow allocate GBs (exit 0); SR-44 runtime cap is dead code #439

Description

@avrabe

Summary

kilnd --memory <bytes> bounds linear memory but does not bound table memory at all — neither the eager initial min-element allocation nor table.grow. A 45-byte module declaring a huge table min allocates multiple GB under a nominal 64 KB cap and runs to exit 0 ("completed successfully"), silently. The SR-44 runtime table cap that #420 claims to have added is dead code (its setter has zero live callers), and there is no reject-at-load for oversized table mins the way there is for memory.

This is the #411/#412 pattern (a cap mechanism that exists but is never wired) applied to tables. Distinct from #436 (that's memory-min, which is eventually rejected, just post-instantiate).

Executed repro (release kilnd v0.4.1)

(module (table 100000000 funcref) (func (export "_start")))   ;; 45 bytes
kilnd --memory 65536 big.wasm
  → ✓ Function '_start' completed  /  Module execution completed successfully   (exit 0)
  → peak RSS 3.02 GB
baseline (table 1):            peak RSS 53 MB
wasmtime run --invoke _start:  ~19 MB

table.grow (SR-44's exact target) is equally uncapped:

(module (table 1 funcref)
  (func (export "_start") (drop (table.grow 0 (ref.null func) (i32.const 80000000)))))

→ under --memory 65536: peak RSS 2.43 GB, exit 0.

Contrast proving it's a gap, not by-design: the same daemon does reject an oversized linear-memory min —

kilnd --memory 65536  (module (memory 2000) ...)
  → ✗ [Resource][E03F8] Module declared memory min exceeds --memory cap (rejected at load)   (exit 1)

Memory is guarded; tables get no check at all. Amplification ≈ 70-million-× (45 B → 3 GB) under a 64 KB cap. Silent (no warning, exit 0).

Root cause (source-verified)

  • kiln-runtime/src/table.rs Table::new: let elements = vec![init_val; initial_size]; where initial_size = ty.limits.minno cap check; runtime_max_elements is initialized to 0 (= unlimited).
  • SR-44's Table::set_runtime_max_elements (table.rs:288) has zero live callers — the only two are inside #[test] fns (runtime_cap_bounds_table_grow_below_declared_max @977, runtime_cap_bounds_mut_grow_path_too @1005). So runtime_max_elements never leaves 0, and the grow cap it feeds never fires.
  • kilnd/src/lib.rs:865-889 (the SR-41/SR-43 cap block) only ever touches inst.memory(0) (set_runtime_max_pages + the memory-min reject). There is no .table(...) handling, and kilnd has no --table CLI flag (--help lists only --memory / --memory-profile).

Relation to prior work

Suggested fix

Wire a table element cap the same way memory is wired: (a) derive a max-elements bound (from --memory, or a new --table cap), (b) reject-at-load when a declared table min exceeds it (mirror the SR-43 memory path), and (c) call set_runtime_max_elements on each instantiated table so the existing grow guard actually fires.

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