cfg_sane() is where a container stops being untrusted input: every dimension that sizes an allocation or bounds a loop is checked there, and #63's Qwen block does that carefully for fifteen of them. Two are missing, and each one is reachable from a manifest.
1. shared_expert_intermediate_size is unbounded, and the shared expert writes past m->ff
m->ff is sized from the routed and dense widths only:
m->ff = (float *)calloc((size_t)2 * (c->dense_inter > c->moe_inter
? c->dense_inter : c->moe_inter), sizeof(float));
but qwen_moe_layer batches the shared expert's gate and up projections into it at shared_inter, which comes straight from the manifest and is never compared against either:
{ m->ff, ...shared_expert.gate_proj.weight..., shared_in },
{ m->ff + shared_in, ...shared_expert.up_proj.weight..., shared_in },
validate_qwen_tensors does not catch it — it derives the expected shape from the same shared_inter, so a container whose tensors agree with its own manifest passes.
Reproduced by regenerating the synthetic fixture with shared_expert_intermediate_size: 512 over moe_intermediate_size: 16 and the three shared_expert.* tensors shaped from the former rather than the latter. Under a plain build it loads, runs and prints logits. Under ASan:
ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 4 at 0x60c000000780 ... in waste_mvq4_rows_i8mm simd_i8mm.c:73
#1 mvb_pieces model.c:1034
#2 waste__worker threads.h:197
0 bytes after 128-byte region [0x60c000000700,0x60c000000780)
allocated by thread T0 in waste_model_load model.c:3272
A write, not a read, and off the calling thread. It survives on Flash-Next only by coincidence: shared_expert_intermediate_size and moe_intermediate_size are both 640 there, so the buffer fits exactly.
Either bound it — shared_inter <= max(moe_inter, dense_inter) — or size m->ff from shared_inter as well. The bound is the better half of the pair: it says which containers this engine will run, which is what cfg_sane is for.
2. ngram_size may be 1, and validate_qwen_tensors divides by ngram_size - 1
const int width = (c->ple_embed && c->heads_per_ngram)
? c->ple_embed / ((c->ngram_size - 1) * c->heads_per_ngram) : 8;
cfg_sane admits ngram_size >= 1, and the guard in front of the division tests the two factors that are not zero. With ngram_size: 1 and a PLE layer the divisor is zero.
This is the same failure the file already documents a few hundred lines further down, where group was 0 on a skipped tensor:
arm64's sdiv quietly yields 0, x86's idiv raises #DE, so waste info on K3 was an instant SIGFPE on every x86 build while every check here stayed green. issue #10
Same split here. Editing the fixture's manifest to ngram_size: 1 on an arm64 build gives width == 0 and a refusal for the wrong reason ("required tensor is missing or has the wrong shape"); compiling the expression for both ISAs gives rc=0 on arm64 and rc=136 — SIGFPE — on x86_64. Every x86 CI job would be green and every x86 user would get a crash.
Requiring ngram_size >= 2 when ple_layer >= 0 covers it, and it is what the PLE path actually needs: ngram_size: 1 produces zero heads and an all-zero embedding anyway.
3. Same family, smaller: linear_conv_kernel_dim is not required to be >= 1 for Qwen
The conv_k < 1 test in cfg_sane sits on the non-Qwen branch. With KS == 0, gdn_conv_range computes ring + b * (KS - 1) — a negative offset — and REQUIRE_DATA(conv1d.weight, qkv * 0) asks for nothing, so the container is accepted.
Checks
tests/run.sh already has the right shape for this: five qwen_refused cases, each a one-line manifest edit. Three more entries there — an oversized shared expert, ngram_size: 1, linear_conv_kernel_dim: 0 — would pin all three, and the first is worth running under make asan too, since a plain build does not notice it.
Found reviewing #63; the code is on feature/qwen38-flash-next and not yet on main. See also #68, from the same review.
cfg_sane()is where a container stops being untrusted input: every dimension that sizes an allocation or bounds a loop is checked there, and #63's Qwen block does that carefully for fifteen of them. Two are missing, and each one is reachable from a manifest.1.
shared_expert_intermediate_sizeis unbounded, and the shared expert writes pastm->ffm->ffis sized from the routed and dense widths only:but
qwen_moe_layerbatches the shared expert's gate and up projections into it atshared_inter, which comes straight from the manifest and is never compared against either:{ m->ff, ...shared_expert.gate_proj.weight..., shared_in }, { m->ff + shared_in, ...shared_expert.up_proj.weight..., shared_in },validate_qwen_tensorsdoes not catch it — it derives the expected shape from the sameshared_inter, so a container whose tensors agree with its own manifest passes.Reproduced by regenerating the synthetic fixture with
shared_expert_intermediate_size: 512overmoe_intermediate_size: 16and the threeshared_expert.*tensors shaped from the former rather than the latter. Under a plain build it loads, runs and prints logits. Under ASan:A write, not a read, and off the calling thread. It survives on Flash-Next only by coincidence:
shared_expert_intermediate_sizeandmoe_intermediate_sizeare both 640 there, so the buffer fits exactly.Either bound it —
shared_inter <= max(moe_inter, dense_inter)— or sizem->fffromshared_interas well. The bound is the better half of the pair: it says which containers this engine will run, which is whatcfg_saneis for.2.
ngram_sizemay be 1, andvalidate_qwen_tensorsdivides byngram_size - 1cfg_saneadmitsngram_size >= 1, and the guard in front of the division tests the two factors that are not zero. Withngram_size: 1and a PLE layer the divisor is zero.This is the same failure the file already documents a few hundred lines further down, where
groupwas 0 on a skipped tensor:Same split here. Editing the fixture's manifest to
ngram_size: 1on an arm64 build giveswidth == 0and a refusal for the wrong reason ("required tensor is missing or has the wrong shape"); compiling the expression for both ISAs givesrc=0on arm64 andrc=136— SIGFPE — on x86_64. Every x86 CI job would be green and every x86 user would get a crash.Requiring
ngram_size >= 2whenple_layer >= 0covers it, and it is what the PLE path actually needs:ngram_size: 1produces zero heads and an all-zero embedding anyway.3. Same family, smaller:
linear_conv_kernel_dimis not required to be >= 1 for QwenThe
conv_k < 1test incfg_sanesits on the non-Qwen branch. WithKS == 0,gdn_conv_rangecomputesring + b * (KS - 1)— a negative offset — andREQUIRE_DATA(conv1d.weight, qkv * 0)asks for nothing, so the container is accepted.Checks
tests/run.shalready has the right shape for this: fiveqwen_refusedcases, each a one-line manifest edit. Three more entries there — an oversized shared expert,ngram_size: 1,linear_conv_kernel_dim: 0— would pin all three, and the first is worth running undermake asantoo, since a plain build does not notice it.Found reviewing #63; the code is on
feature/qwen38-flash-nextand not yet onmain. See also #68, from the same review.