Security context
Low· 5.3GHSA-qfhq-4f3w-5fph CVE-2025-3001CWE-119Published Mar 31, 2025

PyTorch is vulnerable to memory corruption through its torch.lstm_cell function

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

0 → fixed in 2.10.0

Details

A vulnerability classified as critical was found in PyTorch 2.6.0. This vulnerability affects the function torch.lstm_cell. The manipulation leads to memory corruption. The attack needs to be approached locally. The exploit has been disclosed to the public and may be used. A patch is available through commit [999d94b](https://github.com/pytorch/pytorch/commit/999d94b5ede5f4ec111ba7dd144129e2c2725b03).

The fix

Fix segmentation fault caused by invalid gate weight size in

Yuxingwang-intel· Dec 19, 2025, 10:20 AM+471999d94b5ed
aten/src/ATen/native/RNN.cpp+14 1
@@ -695,6 +695,15 @@ void check_rnn_cell_forward_hidden(const Tensor& input, const Tensor& hx, const
"hidden", hidden_label, " has inconsistent hidden_size: got ", hx.sym_size(1), ", expected ", hidden_size);
}
+template<int64_t gate_count>
+inline void check_rnn_cell_forward_weights(const Tensor& w_ih, const Tensor& w_hh, const c10::SymInt& hidden_size){
+ TORCH_CHECK(w_ih.size(0) == gate_count * hidden_size, "weight_ih first dim must be ", gate_count, " * hidden_size = ",
+ gate_count * hidden_size, ", but got ", w_ih.size(0));
+ TORCH_CHECK(w_hh.size(0) == gate_count * hidden_size, "weight_hh first dim must be ", gate_count, " * hidden_size = ",
+ gate_count * hidden_size, ", but got ", w_hh.size(0));
+}
+
+
template<typename hidden_type_tmpl, typename cell_params_tmpl>
struct Cell {
using hidden_type = hidden_type_tmpl;
@@ -1537,8 +1546,9 @@ std::tuple<Tensor, Tensor> lstm_cell(
const Tensor& b_hh = b_hh_opt.value_or(Tensor());
TORCH_CHECK(hx.size() == 2, "lstm_cell expects two hidden states");
- check_rnn_cell_forward_input(input, w_ih.sym_size(1));
auto hidden_size = w_hh.sym_size(1);
+ check_rnn_cell_forward_input(input, w_ih.sym_size(1));
+ check_rnn_cell_forward_weights<4>(w_ih, w_hh, hidden_size);
check_rnn_cell_forward_hidden(input, hx[0], hidden_size, 0);
check_rnn_cell_forward_hidden(input, hx[1], std::move(hidden_size), 1);
static at::Tensor undefined;
@@ -1652,6 +1662,7 @@ Tensor gru_cell(
check_rnn_cell_forward_input(input, w_ih.size(1));
check_rnn_cell_forward_hidden(input, hx, w_hh.size(1), 0);
+ check_rnn_cell_forward_weights<3>(w_ih, w_hh, w_hh.size(1));
static at::Tensor undefined;
return GRUCell<CellParams>{}(input, hx, CellParams{w_ih, w_hh, b_ih, b_hh, undefined});
}
@@ -1665,6 +1676,7 @@ Tensor rnn_tanh_cell(
const Tensor& b_hh = b_hh_opt.value_or(Tensor());
static at::Tensor undefined;
+ check_rnn_cell_forward_weights<1>(w_ih, w_hh, w_hh.size(1));
check_rnn_cell_forward_input(input, w_ih.size(1));
check_rnn_cell_forward_hidden(input, hx, w_hh.size(1), 0);
return SimpleCell<tanh_f, CellParams>{}(input, hx, CellParams{w_ih, w_hh, b_ih, b_hh, undefined});
@@ -1679,6 +1691,7 @@ Tensor rnn_relu_cell(
const Tensor& b_hh = b_hh_opt.value_or(Tensor());
static at::Tensor undefined;
+ check_rnn_cell_forward_weights<1>(w_ih, w_hh, w_hh.size(1));
check_rnn_cell_forward_input(input, w_ih.size(1));
check_rnn_cell_forward_hidden(input, hx, w_hh.size(1), 0);
return SimpleCell<relu_f, CellParams>{}(input, hx, CellParams{w_ih, w_hh, b_ih, b_hh, undefined});
test/test_nn.py+33 0
@@ -7555,6 +7555,39 @@ def test_pickle_module_no_weights_only_warning(self):
pickle.loads(pickle.dumps(torch.nn.Linear(10, 10)))
self.assertEqual(len(w), 0)
+ def test_rnn_cell_gate_weights_size(self):
+ def test_rnn_cell(cell_fn, gate_count):
+ input_size = 8
+ hidden_size = 16
+ x = torch.randn(4, input_size)
+ hx = torch.randn(4, hidden_size)
+ cx = torch.randn(4, hidden_size)
+
+ w_ih_invalid = torch.randn((gate_count * hidden_size) + 1, 8)
+ w_ih = torch.randn(gate_count * hidden_size, 8)
+ w_hh_invalid = torch.randn((gate_count * hidden_size) + 1, 16)
+ w_hh = torch.randn(gate_count * hidden_size, 16)
+ b_ih = torch.randn(gate_count * hidden_size)
+ b_hh = torch.randn(gate_count * hidden_size)
+
+ if cell_fn is torch.lstm_cell:
+ state = (hx, cx)
+ else:
+ state = hx
+
+ with self.assertRaisesRegex(RuntimeError, "weight_ih"):
+ cell_fn(x, state, w_ih_invalid, w_hh, b_ih, b_hh)
+
+ with self.assertRaisesRegex(RuntimeError, "weight_hh"):
+ cell_fn(x, state, w_ih, w_hh_invalid, b_ih, b_hh)
+ for cell_fn, gate_count in [
+ (torch.lstm_cell, 4),
+ (torch.gru_cell, 3),
+ (torch.rnn_relu_cell, 1),
+ (torch.rnn_tanh_cell, 1),
+ ]:
+ test_rnn_cell(cell_fn, gate_count)
+
class TestFusionEval(TestCase):
@set_default_dtype(torch.double)
@given(X=hu.tensor(shapes=((5, 3, 5, 5),), dtype=np.double),

References