Skip to content

fix(security): validate INT4 W4A8 MoE inputs to prevent OOB read and …#48580

Open
jperezdealgaba wants to merge 2 commits into
vllm-project:mainfrom
jperezdealgaba:fix/int4-moe-oob-validation
Open

fix(security): validate INT4 W4A8 MoE inputs to prevent OOB read and …#48580
jperezdealgaba wants to merge 2 commits into
vllm-project:mainfrom
jperezdealgaba:fix/int4-moe-oob-validation

Conversation

@jperezdealgaba

Copy link
Copy Markdown
Contributor

The CPU fused-MoE INT4_W4A8 path derived its intermediate size N and
group count from the w1_scale tensor shape (attacker-controlled checkpoint
geometry), skipped the weight-shape checks that every other quant branch
performs, and never validated the scale/zero sizes.
A crafted int4 MoE checkpoint that inflates the scale shape while shipping
small packed weights makes the tinygemm read out of bounds of
packed_w1/packed_w2 (CWE-125). num_groups = 0 additionally causes a
divide-by-zero (CWE-369).

…div-by-zero

The CPU fused-MoE INT4_W4A8 path derived its intermediate size N and
group count from the w1_scale tensor shape (attacker-controlled checkpoint
geometry), skipped the weight-shape checks that every other quant branch
performs, and never validated the scale/zero sizes.

A crafted int4 MoE checkpoint that inflates the scale shape while shipping
small packed weights makes the tinygemm read out of bounds of
packed_w1/packed_w2 (CWE-125). num_groups = 0 additionally causes a
divide-by-zero (CWE-369).

Validations added:
- w1_scale/w2_scale/w1_zero/w2_zero presence and dimensionality checks
  before deriving N from scale shape
- num_groups > 0 guard to prevent divide-by-zero
- Cross-validate scale N-block count against packed weight dimensions
  to prevent inflated scale shapes from causing OOB reads
- Full scale/zero element count validation (matching INT8, FP8, MXFP4)
- Packed weight size check against stride computed from scale-derived N

Signed-off-by: Juan Perez de Algaba <jperezde@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

Signed-off-by: jperezde <jperezde@redhat.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@mergify mergify Bot added the cpu Related to CPU backends label Jul 14, 2026
Comment thread csrc/cpu/sgl-kernels/moe.cpp Outdated
…ulations

Use __int128 wide arithmetic for required_w1, required_w2, and
buffer_size_nbytes multiplication chains to prevent integer overflow
from bypassing the size validation checks. An attacker-controlled
checkpoint geometry with large dimensions could wrap around int64_t,
making the subsequent TORCH_CHECK pass despite actual buffer sizes being
insufficient.

Signed-off-by: Juan Perez de Algaba <jperezde@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

Signed-off-by: jperezde <jperezde@redhat.com>
int64_t buffer_size_nbytes =
M * topk * N * 2 + M * topk * K * 2 +
num_threads * BLOCK_M * K *
__int128 buffer_size_wide =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: LOW

The buffer size calculation introduces an integer overflow risk. While buffer_size_wide is checked against the maximum limit of int64_t, subsequent additions for INT4_W4A8 and other quantization methods are performed directly on buffer_size_nbytes without overflow validation, potentially leading to heap-based buffer out-of-bounds writes.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Move all branch-specific buffer size additions (for INT8_W8A8, FP8_W8A16/MXFP4, and INT4_W4A8 on lines 1025-1034) into the __int128 buffer_size_wide accumulation before the overflow check on line 1021. This ensures the complete allocation size is validated against int64_t limits before the downcast.

Specifically:

  1. Remove the three if blocks that add to buffer_size_nbytes (lines 1025-1034).
  2. Instead, add those same quantities to buffer_size_wide (using static_cast<__int128>(...) casts for each multiplicand) before the TORCH_CHECK.
  3. Also protect the sub-expressions inside std::max() from int64_t overflow by computing them in __int128, e.g.:
    std::max(static_cast<__int128>(M) * K, static_cast<__int128>(M) * topk * N)
  4. After the single TORCH_CHECK, cast buffer_size_wide to int64_t to get buffer_size_nbytes.

This consolidates all size arithmetic under the existing __int128 overflow guard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cpu Related to CPU backends

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant