PyTorch is vulnerable to memory corruption through its torch.lstm_cell function
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
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
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
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2025-3001
- WEBhttps://github.com/pytorch/pytorch/issues/149626
- WEBhttps://github.com/pytorch/pytorch/issues/149626#issue-2935860995
- WEBhttps://github.com/pytorch/pytorch/commit/999d94b5ede5f4ec111ba7dd144129e2c2725b03
- WEBhttps://github.com/pypa/advisory-database/tree/main/vulns/torch/PYSEC-2025-195.yaml
- PACKAGEhttps://github.com/pytorch/pytorch
- WEBhttps://vuldb.com/?ctiid.302050
- WEBhttps://vuldb.com/?id.302050
- WEBhttps://vuldb.com/?submit.524212