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.min — no 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.
Summary
kilnd --memory <bytes>bounds linear memory but does not bound table memory at all — neither the eager initialmin-element allocation nortable.grow. A 45-byte module declaring a huge tableminallocates 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)
table.grow(SR-44's exact target) is equally uncapped:→ 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 —
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.rsTable::new:let elements = vec![init_val; initial_size];whereinitial_size = ty.limits.min— no cap check;runtime_max_elementsis initialized to0(= unlimited).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). Soruntime_max_elementsnever leaves 0, and thegrowcap it feeds never fires.kilnd/src/lib.rs:865-889(the SR-41/SR-43 cap block) only ever touchesinst.memory(0)(set_runtime_max_pages+ the memory-min reject). There is no.table(...)handling, and kilnd has no--tableCLI flag (--helplists only--memory/--memory-profile).Relation to prior work
25da5f94, "table runtime cap + reject-at-load on oversized memory min (SR-43, SR-44)") landed the memory-min reject (SR-43) and theset_runtime_max_elementsmechanism, but the SR-44 table half was never wired into the daemon — the setter is called only from tests. So SR-44 / AD-WCMC-001 ("close the uncapped-table.grow gap") is not actually closed.Suggested fix
Wire a table element cap the same way memory is wired: (a) derive a max-elements bound (from
--memory, or a new--tablecap), (b) reject-at-load when a declared tableminexceeds it (mirror the SR-43 memory path), and (c) callset_runtime_max_elementson each instantiated table so the existinggrowguard actually fires.