Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/changelog.d/population-label-int64-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Reject population-label int64 narrowing

## Security

- Reject unsigned values above the signed 64-bit boundary and floating-point
values that would be saturated by NumPy during population-label compaction.
- Preserve the largest exact signed `int64` label while keeping group and
cluster identifiers compact before Rust-owned allocation.
5 changes: 5 additions & 0 deletions python/fast_mlsirm/fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ def _compact_population_labels(raw, n_persons: int, name: str):
raise ValueError(f"{name} must be finite")
if _np.any(fl < 0) or _np.any(fl != _np.floor(fl)):
raise ValueError(f"{name} must be non-negative integers")
int64_max = _np.iinfo(_np.int64).max
if arr.dtype.kind == "u" and _np.any(arr > _np.uint64(int64_max)):
raise ValueError(f"{name} must fit in signed 64-bit integers")
if arr.dtype.kind == "f" and _np.any(fl >= _np.float64(2**63)):
Comment thread
seonghobae marked this conversation as resolved.
Resolved
raise ValueError(f"{name} must fit in signed 64-bit integers")
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
Comment thread
seonghobae marked this conversation as resolved.
Resolved
try:
with _np.errstate(invalid="ignore", over="ignore"):
int_labels = arr.astype(_np.int64)
Expand Down
8 changes: 8 additions & 0 deletions tests/test_population_label_int64_roundtrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ def test_population_labels_reject_float_int64_narrowing_overflow_without_warning
_compact_population_labels(labels, 2, "group_id")


def test_population_labels_reject_integer_sequence_promoted_to_float64() -> None:
"""A Python sequence containing 2**63 must not be silently saturated."""
labels = [0, 2**63]

with pytest.raises(ValueError, match="signed 64-bit"):
_compact_population_labels(labels, 2, "group_id")
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved


def test_population_labels_preserve_signed_int64_upper_boundary() -> None:
"""The largest valid signed label remains admissible and order preserving."""
labels = np.array([0, np.iinfo(np.int64).max], dtype=np.int64)
Expand Down
Loading