Skip to content

qwen: refuse a PLE conv kernel the ring cannot hold - #1

Merged
Skibisky merged 1 commit into
Skibisky:feature/qwen38-flash-nextfrom
mfethe1:pr63-ple-conv-fix
Sep 13, 2026
Merged

Skibisky merged 1 commit into
Skibisky:feature/qwen38-flash-nextfrom
mfethe1:pr63-ple-conv-fix

Conversation

@mfethe1

@mfethe1 mfethe1 commented Sep 13, 2026

Copy link
Copy Markdown

Follow-up to the heap-buffer-overflow discussion in sqliteai#63 (marcobambini's ASan report, root-caused in my comment).

What this fixes

A container with ple_conv_kernel_size = 0 declares no kernel, but two defensive > 0 ? … : 4 fallbacks let it through the loader:

  • the ring is allocated (ple_conv_k - 1) * ngram_size → 0 elements;
  • REQUIRE_DATA validates the conv weight as if the kernel were 4 and passes;
  • the step then walks KS = 4 taps across a zero-length ring → heap-buffer-overflow READ inside qwen_dilated_conv_step off m->ple_ring.

The fix — refuse, don't normalize

ple_conv_k is bounded 1..64 in cfg_sane(), but only for containers that declare a PLE layer (ple_layer >= 0); the field stays 0 and meaningless for every other architecture, so kimi/GLM containers are untouched. Both > 0 ? … : 4 fallbacks are deleted so one stated meaning survives end to end: the container's own declaration.

Verified (ASan+UBSan, -fno-sanitize-recover=all, Apple M4)

  • red-before on base c8040c6: synthetic Qwen container with ple_conv_kernel_size 4 → 0 aborts under ASan — heap-buffer-overflow READ in qwen_dilated_conv_step off m->ple_ring.
  • green-after: the zero container is now refused by both the loader and waste info (exit 1, no sanitizer report); the valid control runs unchanged.
  • valid control bit-for-bit identical: logits sha256 469a7433b7910b3f… identical before and after the fix.
  • full suite: make asan 76 passed / 0 failed / 17 skipped vs base 75/0/17 — the +1 is the new qwen_refused "a PLE conv kernel the ring cannot hold" case beside the existing layer_types refusals in tests/run.sh, so it runs in the normal suite.

cc @Skibisky (you asked for this on sqliteai#63), @marcobambini

A container with ple_conv_kernel_size = 0 declares no kernel, but two
defensive fallbacks ('> 0 ? value : 4') let it through the loader: the
weight check validates as if the kernel were 4 while the ring is
allocated (ple_conv_k - 1) * ngram_size = 0 elements, so the conv step
walks 4 taps across a zero-length ring — a heap-buffer-overflow READ
inside qwen_dilated_conv_step off m->ple_ring.

Refuse, don't normalize: ple_conv_k is now bounded 1..64 in cfg_sane(),
but only for containers that declare a PLE layer (ple_layer >= 0); the
field stays 0 and meaningless for every other architecture. Both
fallbacks are deleted so one stated meaning survives end to end:
the container's own declaration.

Verified on the synthetic Qwen container (make-test-container --qwen):
- base: test_forward with ple_conv_kernel_size 4 -> 0 aborts under
  ASan+UBSan (heap-buffer-overflow READ in qwen_dilated_conv_step)
- fixed: the zero container is refused by the loader and waste info,
  no sanitizer report
- valid control logits bit-for-bit identical before/after:
  sha256 469a7433b7910b3f... both arms
- make asan suite: 76 passed / 0 failed / 17 skipped (base: 75/0/17);
  the +1 is the new 'PLE conv kernel the ring cannot hold' refusal case
  beside the existing layer_types refusals in tests/run.sh

Follow-up to the review discussion in sqliteai#63.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants