Skip to content

fix(capi): spir_uhat_get_default_matsus still lacks companion output-size query and buffer validation (residual of #199) #255

Description

Summary

Closed High-severity issue #199 — fix(capi): buffer overflow in multiple functions writing to caller buffers was closed as "Addressed in PR #218", but item 2 of that issue — spir_uhat_get_default_matsus lacking a companion output-size query function and output-buffer validation — was never fixed. The function still writes points_vec.len() elements into the caller-owned points buffer with no capacity check and no way for the caller to learn the required size in advance.

Audited commit: 057996207ed633a3786588539f869e689235983c (origin/main, 2026-08-18).

Referenced closed issue

Original claim (issue #199, item 2)

spir_uhat_get_default_matsus (funcs.rs:795-797): No companion function to query output size.

The proposed fix in #199 was: "Add out_len parameters and validate before writing, or at minimum document exact buffer size requirements."

What PR #218 actually did

PR #218 (diff) did not touch spir_uhat_get_default_matsus. Its only sparse-ir-capi/src/funcs.rs change was the spir_funcs_is_assigned null-check hardening (issue #200). The maintainer's closure comment confirms the narrow scope:

"Addressed in PR #218: documented that segments_x/y callers must allocate n_segments+1 elements for boundary points."

That documents only the item-3 segments_x/y off-by-one; item 2 (the uhat getter) is not covered.

Current evidence

spir_uhat_get_default_matsus (sparse-ir-capi/src/funcs.rs:737-808, commit 0579962) still takes no output-length/capacity parameter, has no companion size-query function, and performs an unchecked copy:

// funcs.rs:801-803
let n_points = points_vec.len();
std::ptr::copy_nonoverlapping(points_vec.as_ptr(), points, n_points);
*n_points_returned = n_points as libc::c_int;
  • No spir_uhat_get_n_default_matsus (or any size-query variant) exists anywhere in sparse-ir-capi, fortran, python, cxx_tests, or julia (verified by grep and the checked-in header).
  • The generated header declares the same signature and only warns, with no size-query companion:
    • Declaration: sparse-ir-capi/include/sparseir/sparseir.h:969-974.
    • Doc: "The size of the array must be sufficient for the returned points (may exceed L if mitigate is true)" (sparseir.h:954) — it does not state an exact bound, and there is no API to learn the count before allocating.

The overflow path is real: spir_uhat_get_default_matsus calls FiniteTempBasis::default_matsubara_sampling_points_impl(..., fence=mitigate, ...) (sparse-ir/src/basis.rs:502-562). With mitigate=true, fence_matsubara_sampling (sparse-ir/src/basis.rs:434-500) appends up to two extra frequencies per outer frequency once the base vector reaches size 20 (resp. 42), so the returned count can exceed the requested l. The function then writes all of those elements with no check against the caller's buffer.

Contrast with the same issue's item 1, which was remediated: spir_basis_get_default_matsus_ext now truncates to the caller capacity (n_to_return = matsu_points.len().min(n_points as usize), sparse-ir-capi/src/basis.rs:1211) and gained the companion spir_basis_get_n_default_matsus_ext (basis.rs:1126-1155).

Expected behavior

Per the C ABI Safety rules, spir_uhat_get_default_matsus must give the caller a way to determine the output size before allocating and must validate capacity before writing to caller-owned buffers — the same pattern applied to the basis getter and, for the sibling residual, to the SVE hint getters in PR #218.

Observed gap

  • Item 2 of fix(capi): buffer overflow in multiple functions writing to caller buffers #199 (companion output-size query function and/or output-buffer validation for spir_uhat_get_default_matsus) was never applied; the issue was closed as "Addressed" without covering this function.
  • spir_uhat_get_default_matsus writes points_vec.len() i64 elements with no capacity validation. With mitigate=true and a full Matsubara grid (l around 20+), the fence can produce more points than l; a caller allocating l elements (the natural reading of the l "Number of requested sampling points" argument) triggers an out-of-bounds write. Because there is no companion size-query, the two-pass "call to learn size, then allocate" protocol cannot even be started safely (the first call already writes the full vector).
  • This is the same buffer-overflow hazard class the closed High-severity issue fix(capi): buffer overflow in multiple functions writing to caller buffers #199 was created to eliminate, still present on a public ABI entry point.

Impact

Potential heap buffer overflow (out-of-bounds write) in a public C ABI entry point reachable with valid parameters (mitigate=true, non-positive-only grid, l >= ~20). C/C++/Fortran/Python callers that allocate l elements and pass mitigate=true are affected. The write is silent: the function returns SPIR_COMPUTATION_SUCCESS.

Violated repository-local rule

  • REPOSITORY_RULES.md:126-128 — "Validate output capacity, shape, and element type before writing. Unless an API explicitly documents partial output on failure, validation must complete before mutating caller-owned output buffers."
  • REPOSITORY_RULES.md:121-123 — "Before constructing a slice or tensor view from raw parts, validate pointer requirements and use checked arithmetic for dimension products, element counts, byte lengths, strides, and offsets."
  • REPOSITORY_RULES.md:116-118 (C ABI Safety) — Rust must check every verifiable precondition before dereferencing, including lengths and arithmetic bounds.

These repository-local rules override the shared tensor4all-agent-rules rules/common/repository.md when they conflict.

Verification limitations

  • Verified at source level against commit 0579962 (origin/main): the unchecked copy_nonoverlapping of points_vec.len() elements and the absence of any companion size-query function are directly confirmed in the code and checked-in header.
  • The overflow mechanism (fence appends frequencies when the base vector reaches size 20/42) is confirmed by inspection of fence_matsubara_sampling; the identical mechanism was empirically reproduced in the sibling issue #239 (spir_basis_get_default_matsus_ext returning 32 points for a requested 30 with mitigate=true). The full C ABI call for this specific function was not compiled/linked here to avoid a broad workspace build; the uhat getter shares the exact same sampling path (default_matsubara_sampling_points_impl with fence=mitigate).
  • No maintainer confirmation obtained; finding is based on source inspection plus the shared, previously reproduced fence behavior.

Duplicate check

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions