fix(ck): prevent int32 overflow in tensor descriptor element space size - #10547
Conversation
calculate_element_space_size_impl() and the fallback lambda in make_naive_tensor_descriptor() computed (lengths[i] - 1) * strides[i] in index_t (int32) before widening into the long_index_t accumulator. For tensors where a dimension product exceeds INT32_MAX (e.g. K=C=65537 in grouped conv bwd weight), the multiply wrapped and GetElementSpaceSize() severely undercounted the workspace, while the kernel launch path sized the same region correctly via accumulate_n<long_index_t> -- an overflow -> OOB write mismatch (CWE-190 -> CWE-787). Cast both operands to long_index_t before the multiply at both sites, matching the existing correct pattern in ck_tile/core/tensor/tensor_descriptor.hpp. Fixes ROCM-29071.
✅ All Checks Passed — Ready for Review
📖 Need help? See the Policy FAQ for details on every check and how to fix failures. |
|
🎉 All checks passed! This PR is ready for review. |
There was a problem hiding this comment.
Pull request overview
Fixes 32-bit overflow when calculating Composable Kernel tensor-descriptor element-space sizes.
Changes:
- Widens runtime length and stride multiplication to 64 bits.
- Updates both compiler-workaround and fallback calculation paths.
Suppressed comments (2)
projects/composablekernel/include/ck/tensor_description/tensor_descriptor_helper.hpp:73
- The fallback path has the same compile-time regression: converting
Number<>values to scalarlong_index_tmakes an otherwise static descriptor reportIsKnownAtCompileTime() == false, which excludes it fromStaticTensor. Preserve the integral-constant result while widening the multiplication.
auto acc_new = acc_old + static_cast<long_index_t>(lengths[i] - Number<1>{}) *
static_cast<long_index_t>(strides[i]);
projects/composablekernel/include/ck/tensor_description/tensor_descriptor_helper.hpp:30
- This overflow fix is not covered by a regression test, despite existing GTest coverage for tensor-descriptor helpers in
test/util/unit_tensor_descriptor_functors.cpp. Add the planned runtime{65537, 65537}/{65537, 1}assertion and a fully-static type/IsKnownAtCompileTime()assertion so both the overflow and compile-time descriptor contract are protected.
auto acc_new = acc_old + static_cast<long_index_t>(lengths[i] - Number<1>{}) *
static_cast<long_index_t>(strides[i]);
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…overflow (ROCM-29071) The initial ROCM-29071 fix cast both operands to long_index_t unconditionally. For fully-static tensor descriptors this turned the element space size from a compile-time integral_constant into a runtime long_index_t, so TensorDescriptor::IsKnownAtCompileTime() became false and many static-descriptor consumers failed to instantiate (gfx950/gfx1201/Windows math-libs build breaks). Introduce widen_runtime_index_to_long(): widen only runtime index operands to long_index_t before the multiply, leaving Number<>/LongNumber<> operands untouched. Static descriptors keep a compile-time-constant element space size; the runtime path is still int64 and overflow-safe (K*C > INT32_MAX).
Production source should not point to internal JIRA tickets; ticket context belongs in the PR/commit metadata instead.
GPU verification (MI300X / gfx942, ROCm 6.4.1)Built the actual
The old header reports a 131073-element space for a tensor that actually needs 4.29e9 — i.e. the undersized-workspace → OOB-write mechanism, reproduced on device. The fix returns the correct 64-bit value on host and on the gfx942 device, leaves the sub- Also confirmed at the type level with One caveat on the PR itself: it doesn't add the committed gtest for the |
Add a gtest for make_naive_tensor_descriptor's GetElementSpaceSize() covering the case where (length-1)*stride exceeds INT32_MAX. The test checks the dynamic path on both host and device (must compute in 64-bit), that a sub-INT32_MAX product is unchanged, and that a fully static descriptor keeps IsKnownAtCompileTime() true. Guards the widen fix in tensor_descriptor_helper.hpp against regressions (ROCM-29071).
andriy-ca
left a comment
There was a problem hiding this comment.
Looks good, but can be improved by moving the test in a folder that already exists.
Relocate the GetElementSpaceSize int32-overflow regression test next to the other tensor-descriptor unit tests in test/util (per review), renaming it to the unit_ convention and registering it in test/util/CMakeLists.txt. Drops the separate test/tensor_description subdirectory.
fix(ck): prevent int32 overflow in tensor descriptor element
space size (#10547)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
JIRA ID : ROCM-29071
Fixes ROCM-29071 (synced with SWSPLAT-48860).
## Summary
Fixes a signed 32-bit integer overflow (CWE-190 → CWE-787) in Composable
Kernel's runtime tensor-descriptor construction, reported via bug bounty
as **ROCM-29071**.
`calculate_element_space_size_impl()` (the active path under
`CK_WORKAROUND_SWDEV_275126`) and the fallback lambda in
`make_naive_tensor_descriptor()` both computed:
```cpp
auto acc_new = acc_old + (lengths[i] - Number<1>{}) * strides[i];
```
For a **runtime** (dynamic) descriptor, `lengths[i]` and `strides[i]`
are `index_t` (int32), so the multiply is performed in **32-bit before**
being widened into the `long_index_t` (int64) accumulator. For tensors
where a dimension product exceeds `INT32_MAX`, the product wraps.
**Example (K=C=65537, grouped conv bwd weight):**
- `(65537-1) * 65537 = 4,295,032,832` → wraps to `131,072` (int32)
- `GetElementSpaceSize()` returns `131,073` instead of `4,295,098,369`
- Workspace allocated ≈ 524 KB, but the kernel writes the full ≈ 17 GB
region (sized correctly via `accumulate_n<long_index_t>`) →
out-of-bounds GPU write.
## Fix
Widen **only the runtime operands** to `long_index_t` before the
multiply, via a small helper:
```cpp
template <typename T>
__host__ __device__ constexpr auto widen_runtime_index_to_long(T v)
{
if constexpr(is_number_v<T> || is_long_number_v<T>)
return v; // compile-time operand: leave untouched
else
return static_cast<long_index_t>(v); // runtime operand: widen before multiply
}
```
```cpp
auto acc_new = acc_old + widen_runtime_index_to_long(lengths[i] - Number<1>{})
* widen_runtime_index_to_long(strides[i]);
```
### Why not a blanket `static_cast<long_index_t>` on both operands?
An unconditional cast (the first revision of this PR) also widened the
**compile-time** (`Number<>`) operands, turning the element space size
of a **fully-static** descriptor from a compile-time `integral_constant`
into a runtime `long_index_t`. That flips
`TensorDescriptor::IsKnownAtCompileTime()` to `false`, and every
static-descriptor consumer gated on it (e.g.
`threadwise_tensor_slice_transfer`, contraction/gemm instances) fails to
instantiate — the gfx950 / gfx1201 / Windows `math-libs` build breaks
seen in the previous CI run.
`widen_runtime_index_to_long()` widens the runtime path (fixing the
overflow) while leaving compile-time operands as `Number<>`, so static
descriptors keep a compile-time-constant element space size — identical
to the pre-fix type behavior.
## Affected path
`device_grouped_conv_bwd_weight_xdl_cshuffle.hpp` → `GetWorkSpaceSize()`
→ `GetWorkspaceSizeBytes()` →
`make_naive_tensor_descriptor(...).GetElementSpaceSize()`. Any
conv-bwd-weight (XDL cshuffle) call with `K·C > INT32_MAX`.
## Notes for reviewers
- Both sites are patched so the fix holds regardless of
`CK_WORKAROUND_SWDEV_275126`.
- Mirrors the intent of the already-correct ck_tile pattern
(`ck_tile/core/tensor/tensor_descriptor.hpp`,
`detail::calculate_element_space_size_impl`), adapted to preserve CK's
compile-time `LongNumber<>` element-space-size for static descriptors
(ck_tile instead clamps to a runtime `index_t`).
## Test plan
- [x] Host type-check: a fully-static descriptor keeps a
compile-time-constant element space size (`IsKnownAtCompileTime()` stays
true); the runtime path computes `65536 * 65537 = 4,295,032,832` with no
int32 wrap.
- [x] GPU regression in `test/grouped_convnd_bwd_weight/` at `K·C >
INT32_MAX`: reported workspace size matches the 64-bit
`c_space_size_bytes`; kernel run returns `hipSuccess`.
- [ ] Audit `GetWorkspaceSizeBytes()` / `c_space_size_bytes` callers
agree end-to-end.
fix(ck): prevent int32 overflow in tensor descriptor element space size
JIRA ID : ROCM-29071
Fixes ROCM-29071 (synced with SWSPLAT-48860).
## Summary
Fixes a signed 32-bit integer overflow (CWE-190 → CWE-787) in Composable
Kernel's runtime tensor-descriptor construction, reported via bug bounty
as **ROCM-29071**.
`calculate_element_space_size_impl()` (the active path under
`CK_WORKAROUND_SWDEV_275126`) and the fallback lambda in
`make_naive_tensor_descriptor()` both computed:
```cpp
auto acc_new = acc_old + (lengths[i] - Number<1>{}) * strides[i];
```
For a **runtime** (dynamic) descriptor, `lengths[i]` and `strides[i]`
are `index_t` (int32), so the multiply is performed in **32-bit before**
being widened into the `long_index_t` (int64) accumulator. For tensors
where a dimension product exceeds `INT32_MAX`, the product wraps.
**Example (K=C=65537, grouped conv bwd weight):**
- `(65537-1) * 65537 = 4,295,032,832` → wraps to `131,072` (int32)
- `GetElementSpaceSize()` returns `131,073` instead of `4,295,098,369`
- Workspace allocated ≈ 524 KB, but the kernel writes the full ≈ 17 GB
region (sized correctly via `accumulate_n<long_index_t>`) →
out-of-bounds GPU write.
## Fix
Widen **only the runtime operands** to `long_index_t` before the
multiply, via a small helper:
```cpp
template <typename T>
__host__ __device__ constexpr auto widen_runtime_index_to_long(T v)
{
if constexpr(is_number_v<T> || is_long_number_v<T>)
return v; // compile-time operand: leave untouched
else
return static_cast<long_index_t>(v); // runtime operand: widen before multiply
}
```
```cpp
auto acc_new = acc_old + widen_runtime_index_to_long(lengths[i] - Number<1>{})
* widen_runtime_index_to_long(strides[i]);
```
### Why not a blanket `static_cast<long_index_t>` on both operands?
An unconditional cast (the first revision of this PR) also widened the
**compile-time** (`Number<>`) operands, turning the element space size
of a **fully-static** descriptor from a compile-time `integral_constant`
into a runtime `long_index_t`. That flips
`TensorDescriptor::IsKnownAtCompileTime()` to `false`, and every
static-descriptor consumer gated on it (e.g.
`threadwise_tensor_slice_transfer`, contraction/gemm instances) fails to
instantiate — the gfx950 / gfx1201 / Windows `math-libs` build breaks
seen in the previous CI run.
`widen_runtime_index_to_long()` widens the runtime path (fixing the
overflow) while leaving compile-time operands as `Number<>`, so static
descriptors keep a compile-time-constant element space size — identical
to the pre-fix type behavior.
## Affected path
`device_grouped_conv_bwd_weight_xdl_cshuffle.hpp` → `GetWorkSpaceSize()`
→ `GetWorkspaceSizeBytes()` →
`make_naive_tensor_descriptor(...).GetElementSpaceSize()`. Any
conv-bwd-weight (XDL cshuffle) call with `K·C > INT32_MAX`.
## Notes for reviewers
- Both sites are patched so the fix holds regardless of
`CK_WORKAROUND_SWDEV_275126`.
- Mirrors the intent of the already-correct ck_tile pattern
(`ck_tile/core/tensor/tensor_descriptor.hpp`,
`detail::calculate_element_space_size_impl`), adapted to preserve CK's
compile-time `LongNumber<>` element-space-size for static descriptors
(ck_tile instead clamps to a runtime `index_t`).
## Test plan
- [x] Host type-check: a fully-static descriptor keeps a
compile-time-constant element space size (`IsKnownAtCompileTime()` stays
true); the runtime path computes `65536 * 65537 = 4,295,032,832` with no
int32 wrap.
- [x] GPU regression in `test/grouped_convnd_bwd_weight/` at `K·C >
INT32_MAX`: reported workspace size matches the 64-bit
`c_space_size_bytes`; kernel run returns `hipSuccess`.
- [ ] Audit `GetWorkspaceSizeBytes()` / `c_space_size_bytes` callers
agree end-to-end.
---------
Co-authored-by: Muhammed Emin Ozturk <3836908+ozturkosu@users.noreply.github.com>
Co-authored-by: muozturk <Osman.Ozturk@amd.com>
Co-authored-by: Thrupti Raj Lakshmana Gowda <thruptiraj.lakshmanagowda@amd.com>
JIRA ID : ROCM-29071
Fixes ROCM-29071 (synced with SWSPLAT-48860).
Summary
Fixes a signed 32-bit integer overflow (CWE-190 → CWE-787) in Composable Kernel's runtime tensor-descriptor construction, reported via bug bounty as ROCM-29071.
calculate_element_space_size_impl()(the active path underCK_WORKAROUND_SWDEV_275126) and the fallback lambda inmake_naive_tensor_descriptor()both computed:For a runtime (dynamic) descriptor,
lengths[i]andstrides[i]areindex_t(int32), so the multiply is performed in 32-bit before being widened into thelong_index_t(int64) accumulator. For tensors where a dimension product exceedsINT32_MAX, the product wraps.Example (K=C=65537, grouped conv bwd weight):
(65537-1) * 65537 = 4,295,032,832→ wraps to131,072(int32)GetElementSpaceSize()returns131,073instead of4,295,098,369accumulate_n<long_index_t>) → out-of-bounds GPU write.Fix
Widen only the runtime operands to
long_index_tbefore the multiply, via a small helper:Why not a blanket
static_cast<long_index_t>on both operands?An unconditional cast (the first revision of this PR) also widened the compile-time (
Number<>) operands, turning the element space size of a fully-static descriptor from a compile-timeintegral_constantinto a runtimelong_index_t. That flipsTensorDescriptor::IsKnownAtCompileTime()tofalse, and every static-descriptor consumer gated on it (e.g.threadwise_tensor_slice_transfer, contraction/gemm instances) fails to instantiate — the gfx950 / gfx1201 / Windowsmath-libsbuild breaks seen in the previous CI run.widen_runtime_index_to_long()widens the runtime path (fixing the overflow) while leaving compile-time operands asNumber<>, so static descriptors keep a compile-time-constant element space size — identical to the pre-fix type behavior.Affected path
device_grouped_conv_bwd_weight_xdl_cshuffle.hpp→GetWorkSpaceSize()→GetWorkspaceSizeBytes()→make_naive_tensor_descriptor(...).GetElementSpaceSize(). Any conv-bwd-weight (XDL cshuffle) call withK·C > INT32_MAX.Notes for reviewers
CK_WORKAROUND_SWDEV_275126.ck_tile/core/tensor/tensor_descriptor.hpp,detail::calculate_element_space_size_impl), adapted to preserve CK's compile-timeLongNumber<>element-space-size for static descriptors (ck_tile instead clamps to a runtimeindex_t).Test plan
IsKnownAtCompileTime()stays true); the runtime path computes65536 * 65537 = 4,295,032,832with no int32 wrap.test/grouped_convnd_bwd_weight/atK·C > INT32_MAX: reported workspace size matches the 64-bitc_space_size_bytes; kernel run returnshipSuccess.GetWorkspaceSizeBytes()/c_space_size_bytescallers agree end-to-end.