diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 17b37f6fdd4..0893596d37d 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -63,6 +63,11 @@ constexpr std::chrono::duration RECONNECT_TIMEOUT_MAX{600.0}; * this is belt-and-suspenders sanity limit to prevent memory exhaustion. */ constexpr int MAX_LINE_LENGTH = 100000; +/** Maximum number of lines received on TorControlConnection per reply to avoid + * memory exhaustion. The largest expected now is 5 (PROTOCOLINFO), but future + * changes to this file might need to re-evaluate MAX_LINE_COUNT. + */ +constexpr int MAX_LINE_COUNT = 1000; /** Timeout for socket operations */ constexpr auto SOCKET_SEND_TIMEOUT = 10s; @@ -177,6 +182,9 @@ bool TorControlConnection::ProcessBuffer() auto start = reader.it; while (auto line = reader.ReadLine()) { + if (m_message.lines.size() == MAX_LINE_COUNT) { + throw std::runtime_error(strprintf("Control port reply exceeded %d lines, disconnecting", MAX_LINE_COUNT)); + } // Skip short lines if (line->size() < 4) continue; diff --git a/test/functional/feature_torcontrol.py b/test/functional/feature_torcontrol.py index 9693030b8ac..485ffc44559 100755 --- a/test/functional/feature_torcontrol.py +++ b/test/functional/feature_torcontrol.py @@ -237,11 +237,32 @@ class TorControlTest(BitcoinTestFramework): mock_tor.stop() + def test_overmany_lines(self): + mock_tor = MockTorControlServer(self.next_port(), manual_mode=True) + self.restart_with_mock(mock_tor) + + MAX_LINE_COUNT = 1000 + + self.log.info("Test that Tor control does not disconnect on receiving MAX_LINE_COUNT lines.") + with self.expect_disconnect(False, mock_tor): + for _ in range(MAX_LINE_COUNT - 1): + mock_tor.send_raw("250-Continuing\r\n") + mock_tor.send_raw("250 OK\r\n") + + self.log.info("Test that Tor control disconnects on receiving MAX_LINE_COUNT + 1 lines.") + with self.expect_disconnect(True, mock_tor): + for _ in range(MAX_LINE_COUNT + 1): + mock_tor.send_raw("250-Continuing\r\n") + + mock_tor.stop() + def run_test(self): self.test_basic() self.test_partial_data() self.test_pow_fallback() self.test_oversized_line() + self.test_overmany_lines() + if __name__ == '__main__': TorControlTest(__file__).main()