Guard MlasConvPrepare working-buffer products with SafeInt - #29444
Conversation
MlasConvPrepare computed the im2col working-buffer size as raw size_t multiplies of attacker-controlled tensor shape factors (OutputSize * K, and the thread-partition product TargetThreadCount * MLAS_CONV_WORKING_BUFFER_SIZE_PER_THREAD). The MSVC C26451 arithmetic-overflow lint that flags exactly this class was suppressed by #pragma warning(disable : 26451) rather than the arithmetic being guarded. On wrap, the CPU EP allocates a small working buffer and MlasConvIm2Col then writes the full unwrapped count of floats past it -- heap-buffer-overflow WRITE. Per-tensor element counts are already SafeInt-checked, but the product across tensors was not. Fix: accumulate dim products via SafeInt<size_t> and compute the final working-buffer size with SafeInt-guarded multiplies. SafeInt throws on overflow; the caller propagates as Status failure. Also guards the adjacent BatchCount * GroupCount product. Removes the C26451 suppression block. Adds #include "core/common/safeint.h" (already used elsewhere in core/mlas/lib).
There was a problem hiding this comment.
Pull request overview
This PR hardens MLAS convolution preparation (MlasConvPrepare) against size_t overflow by moving several shape-dependent products into SafeInt<size_t> computations, and adds unit tests that validate overflow cases throw rather than silently producing wrapped sizes.
Changes:
- Switched
InputSize,OutputSize, andKaccumulation inMlasConvPreparetoSafeInt<size_t>and materialized them assize_tonly after checked multiplication. - Guarded working-buffer element-count products (
OutputSize * K,TargetThreadCount * per_thread) usingSafeInt<size_t>. - Added a new unit test file that constructs overflow-inducing shapes and validates exception behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
onnxruntime/core/mlas/lib/convolve.cpp |
Uses SafeInt<size_t> to validate shape-derived products and working-buffer size calculations in MlasConvPrepare. |
onnxruntime/test/mlas/unittest/test_conv_prepare_safeint.cpp |
Adds gtest coverage to ensure overflow cases throw (instead of wrapping) and that small “happy path” inputs still work. |
tianleiwu
left a comment
There was a problem hiding this comment.
Summary
Focused, well-scoped defense-in-depth change. The cross-tensor products in MlasConvPrepare (InputSize/OutputSize/K accumulators, the OutputSize * K im2col buffer size, and the BatchCount * GroupCount thread clamp) are now computed in SafeInt's checked domain, so attacker-controlled shapes that would previously wrap size_t and under-size the working buffer now throw instead of producing a heap-buffer-overflow on the downstream im2col write. Overall this looks good — one non-blocking note below.
Strengths
- Standalone-MLAS split is correct —
SafeInt.hppunderBUILD_MLAS_NO_ONNXRUNTIME,core/common/safeint.hotherwise.safeint_interfaceis on the include path for every MLAS target (theonnxruntime_mlas.cmakeforeach loop), so both branches compile. This resolves the earlier concern about an unconditional ORT-core include in MLAS. SafeInt<size_t>is the right primitive here (vs the ORT-onlySafeMul<>helper incore/common/safeint.h), because it also works in the standalone branch where the ORT helper isn't available.BatchGroupProductreuse removes the duplicatedBatchCount * GroupCountmultiply while preserving behavior; the laterstatic_cast<ptrdiff_t>(BatchGroupProduct)is only reached when the product is<= MaximumThreadCount, so the narrowing stays safe.- Good test construction —
kHalfShiftis portable across 32/64-bit pointer widths; the 3D shapes bypass the KleidiAI 2DMlasConvPrepareOverride; and the non-unit stride keeps execution out of the pointwiseGemmDirectearly return so each guarded product is actually exercised. The!ORT_NO_EXCEPTIONS && !BUILD_MLAS_NO_ONNXRUNTIMEgate and matching#endifcomment are correct, and the tests are auto-collected by the*.cppglob in the MLAS unittest target.
Non-blocking note
- The removed
#pragma warning(disable : 26451)push/pop spanned a broad region (from before the anonymous namespace, throughMlasConvSupportsDenseChannelsLast2DFloatKernel,MlasConvSupportsDepthwiseChannelsLast2DFloatKernel, andMlasConvPrepare), whereas the SafeInt conversion only touchedMlasConvPrepare. The remaining arithmetic in that region is all in thesize_tdomain, so 26451 most likely won't re-fire — but it's worth confirming the MSVC static-analysis leg (onnxruntime_ENABLE_STATIC_ANALYSIS->/analyze) stays warning-clean before merge, since 26451 is a/analyze-only check that is not covered by the target's global/wdsuppressions. If it does re-fire, scoping the pragma to just aroundMlasConvPrepare(rather than removing it entirely) would be the minimal fix.
The earlier automated review comments (unconditional include, test gating, #endif comment text, hard-coded PR number) all appear to be addressed already in the current head.
Addresses non-blocking review feedback on PR #29444: rather than dropping the pragma push/pop entirely, re-add it narrowly around MlasConvPrepare so if /analyze (26451) still fires on the size_t multiplies that feed SafeInt, the static-analysis leg stays clean without silencing the warning for unrelated helpers above.
tianleiwu
left a comment
There was a problem hiding this comment.
Re-review on the latest head
The one non-blocking note from my previous round is now addressed: the #pragma warning(disable : 26451) push/pop is re-added but scoped tightly around MlasConvPrepare instead of spanning the anonymous namespace and the MlasConvSupports* helpers. I confirmed the now-unsuppressed helpers above (ComputeChannelsLastDilatedKernelSize / ComputeChannelsLastConvOutSize) only contain size_t multiplies under #if defined(USE_KLEIDIAI) && defined(MLAS_TARGET_ARM64), so they aren't compiled on the MSVC /analyze leg and 26451 won't re-fire there. The push/pop remain balanced (pop is still at end-of-function).
Verified
- Overflow guard placement is complete for the generic path: the
InputSize/OutputSize/Kaccumulators, theOutputSize * Kim2col buffer size, and theBatchCount * GroupCountthread clamp are all computed in SafeInt's checked domain. The downstream byte allocation in the caller is already guarded (alloc->Alloc(sizeof(float) * SafeInt<size_t>(WorkingBufferSize))incore/providers/cpu/nn/conv.cc), so element-count and byte-count math are both covered. - Portability is correct —
SafeInt.hppunderBUILD_MLAS_NO_ONNXRUNTIMEandcore/common/safeint.hotherwise;safeint_interfaceis on every MLAS target's include path, andBUILD_MLAS_NO_ONNXRUNTIMEis WASM-only where the tests are gated off. Using the rawSafeInt<size_t>primitive (rather than the ORT-onlySafeMul<>helper) is the right call since it also compiles in the standalone branch. - Tests exercise the guarded products —
kHalfShiftoverflows on both 32/64-bit widths, 3D shapes bypass the KleidiAI 2D override, and the non-unit stride keeps execution off the pointwiseGemmDirectearly-return so each SafeInt product is actually reached. Auto-collected by the*.cppglob in the MLAS unittest target.
All earlier automated-review concerns (unconditional ORT-core include, test gating, #endif comment, hard-coded PR number) also remain addressed. LGTM.
This pull request strengthens the safety of arithmetic operations in the convolution implementation by using
SafeIntto prevent integer overflows, especially in cases where tensor shapes may be attacker-controlled. It also removes some compiler-specific warning pragmas that are no longer needed due to these changes.Enhanced integer safety in convolution calculations:
size_tmultiplications withSafeInt<size_t>for calculating input/output sizes and kernel dimensions, ensuring that any arithmetic overflow is caught and handled appropriately. This is particularly important for preventing security vulnerabilities from attacker-controlled tensor shapes. [1] [2]SafeInt<size_t>for all working buffer size calculations, including those involving thread counts and batch/group products, to prevent buffer overflows and ensure correct memory allocation. [1] [2]Code cleanup and maintenance:
SafeIntnow guards against arithmetic overflow, making these warnings obsolete. [1] [2]core/common/safeint.hinclude to provide access to theSafeIntfunctionality.