Security context
Medium· 5.3PYSEC-2025-200 CVE-2025-46150Published Sep 25, 2025

In PyTorch before 2.7.0, when torch.compile is used, FractionalMaxPool2d has inconsistent results.

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

2.6.0 → fixed in 2.7.0

Details

In PyTorch before 2.7.0, when torch.compile is used, FractionalMaxPool2d has inconsistent results.

The fix

Update

Isuru Fernando· Jan 8, 2025, 03:07 PM+2819b9a7e9baff
test/inductor/test_torchinductor.py+8 0
@@ -4626,6 +4626,14 @@ def fn(x):
self.common(fn, (torch.randn(1, 4, 16, 16),), check_lowp=False)
+ def test_fractional_max_pool2d5(self):
+ def fn(x, samples):
+ return aten.fractional_max_pool2d(x, (1, 1), (8, 8), samples)
+
+ self.common(
+ fn, (torch.randn(2, 4, 16, 16), torch.rand(2, 4, 2)), check_lowp=False
+ )
+
def test_multi_threading(self):
model = torch.nn.Linear(2, 3).eval()
inp = torch.randn(4, 2)
torch/_inductor/lowering.py+4 3
@@ -4741,7 +4741,7 @@ def inner_fn_max_idx(idx):
)
-def _fractional_pooling_offsets(samples, in_sz, out_sz, kernel_sz, dim):
+def _fractional_pooling_offsets(samples, in_sz, out_sz, kernel_sz, dim, ndims):
out_sz = out_sz[dim]
in_sz = in_sz[dim]
kernel_sz = kernel_sz[dim]
@@ -4749,10 +4749,10 @@ def _fractional_pooling_offsets(samples, in_sz, out_sz, kernel_sz, dim):
samples_loader = samples.make_loader()
def load(prefix, i):
- sample = samples_loader([*prefix, dim])
+ sample = samples_loader([*prefix, ndims - 1 - dim])
i_expr = ops.index_expr(i, samples.get_dtype())
alpha_expr = ops.index_expr(alpha, samples.get_dtype())
- seq_i = ops.floor((i_expr + sample) * alpha_expr) - ops.floor(
+ seq_i = ops.trunc((i_expr + sample) * alpha_expr) - ops.trunc(
sample * alpha_expr
)
seq_i = ops.to_dtype(seq_i, torch.int64)
@@ -4784,6 +4784,7 @@ def fractional_max_pool2d(x, kernel_size, output_size, random_samples):
in_sz=[inp_h, inp_w],
out_sz=output_size,
kernel_sz=kernel_size,
+ ndims=2,
)
h_index_fn = gen_offsets_for_dim(dim=0)
test/inductor/test_torchinductor.py | 8 --------
.../_internal/common_methods_invocations.py | 16 ++++++++++++++++
2 files changed, 16 insertions(+), 8 deletions(-)
test/inductor/test_torchinductor.py+0 8
@@ -4626,14 +4626,6 @@ def fn(x):
self.common(fn, (torch.randn(1, 4, 16, 16),), check_lowp=False)
- def test_fractional_max_pool2d5(self):
- def fn(x, samples):
- return aten.fractional_max_pool2d(x, (1, 1), (8, 8), samples)
-
- self.common(
- fn, (torch.randn(2, 4, 16, 16), torch.rand(2, 4, 2)), check_lowp=False
- )
-
def test_multi_threading(self):
model = torch.nn.Linear(2, 3).eval()
inp = torch.randn(4, 2)
torch/testing/_internal/common_methods_invocations.py+16 0
@@ -5003,6 +5003,14 @@ def sample_inputs_fractional_max_pool2d(op_info, device, dtype, requires_grad, *
return_indices=return_indices,
)
+ yield SampleInput(
+ make_arg((1, 1, 16, 16)),
+ (1, 1),
+ output_ratio=(0.5, 0.5),
+ return_indices=True,
+ _random_samples=make_tensor((1, 1, 2), device=device, dtype=dtype, requires_grad=False),
+ )
+
def sample_inputs_fractional_max_pool3d(op_info, device, dtype, requires_grad, **kwargs):
make_arg = partial(make_tensor, device=device, dtype=dtype, requires_grad=requires_grad)
@@ -5042,6 +5050,14 @@ def sample_inputs_fractional_max_pool3d(op_info, device, dtype, requires_grad, *
return_indices=return_indices,
)
+ yield SampleInput(
+ make_arg((1, 1, 16, 16, 16)),
+ (1, 1, 1),
+ output_ratio=(0.5, 0.5, 0.5),
+ return_indices=True,
+ _random_samples=make_tensor((1, 1, 3), device=device, dtype=dtype, requires_grad=False),
+ )
+
def sample_inputs_avgpool2d(op_info, device, dtype, requires_grad, **kwargs):
make_arg = partial(make_tensor, device=device, dtype=dtype, requires_grad=requires_grad)
torch/testing/_internal/common_methods_invocations.py | 8 --------
1 file changed, 8 deletions(-)
torch/testing/_internal/common_methods_invocations.py+0 8
@@ -5050,14 +5050,6 @@ def sample_inputs_fractional_max_pool3d(op_info, device, dtype, requires_grad, *
return_indices=return_indices,
)
- yield SampleInput(
- make_arg((1, 1, 16, 16, 16)),
- (1, 1, 1),
- output_ratio=(0.5, 0.5, 0.5),
- return_indices=True,
- _random_samples=make_tensor((1, 1, 3), device=device, dtype=dtype, requires_grad=False),
- )
-
def sample_inputs_avgpool2d(op_info, device, dtype, requires_grad, **kwargs):
make_arg = partial(make_tensor, device=device, dtype=dtype, requires_grad=requires_grad)

References