summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Forcier <jeff@bitprophet.org>2018-09-18 18:21:33 -0700
committerJeff Forcier <jeff@bitprophet.org>2018-09-18 18:21:33 -0700
commit852176d2d776b183a39e100009d3e18b6896323b (patch)
treed31be0ef59abfec60961ac839d4cb7effebc47e6
parent0b2e154b02befa1cd96ebaf39ec597855cf2f8fb (diff)
downloadparamiko-852176d2d776b183a39e100009d3e18b6896323b.tar.gz
Fix a pseudo-bug re: responding to MSG_UNIMPLEMENTED w/ itself
-rw-r--r--dev-requirements.txt1
-rw-r--r--paramiko/transport.py23
-rw-r--r--sites/www/changelog.rst5
-rw-r--r--tests/test_transport.py25
4 files changed, 49 insertions, 5 deletions
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 1da876b8..c192f144 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -4,6 +4,7 @@ invocations>=1.2.0,<2.0
# NOTE: pytest-relaxed currently only works with pytest >=3, <3.3
pytest>=3.2,<3.3
pytest-relaxed==1.1.2
+mock==2.0.0
# Linting!
flake8==2.4.0
# Coverage!
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 1317f372..d8cb3434 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -81,6 +81,8 @@ from paramiko.common import (
DEFAULT_WINDOW_SIZE,
DEFAULT_MAX_PACKET_SIZE,
HIGHEST_USERAUTH_MESSAGE_ID,
+ MSG_UNIMPLEMENTED,
+ MSG_NAMES,
)
from paramiko.compress import ZlibCompressor, ZlibDecompressor
from paramiko.dsskey import DSSKey
@@ -1958,11 +1960,22 @@ class Transport(threading.Thread, ClosingContextManager):
if len(self._expected_packet) > 0:
continue
else:
- self._log(WARNING, "Oops, unhandled type %d" % ptype)
- msg = Message()
- msg.add_byte(cMSG_UNIMPLEMENTED)
- msg.add_int(m.seqno)
- self._send_message(msg)
+ # Respond with "I don't implement this particular
+ # message type" message (unless the message type was
+ # itself literally MSG_UNIMPLEMENTED, in which case, we
+ # just shut up to avoid causing a useless loop).
+ name = MSG_NAMES[ptype]
+ self._log(
+ WARNING,
+ "Oops, unhandled type {} ({!r})".format(
+ ptype, name
+ ),
+ )
+ if ptype != MSG_UNIMPLEMENTED:
+ msg = Message()
+ msg.add_byte(cMSG_UNIMPLEMENTED)
+ msg.add_int(m.seqno)
+ self._send_message(msg)
self.packetizer.complete_handshake()
except SSHException as e:
self._log(ERROR, "Exception: " + str(e))
diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst
index b00f03f0..207bfe59 100644
--- a/sites/www/changelog.rst
+++ b/sites/www/changelog.rst
@@ -2,6 +2,11 @@
Changelog
=========
+- :bug:`-` Modify protocol message handling such that ``Transport`` does not
+ respond to ``MSG_UNIMPLEMENTED`` with its own ``MSG_UNIMPLEMENTED`` message.
+ This behavior probably didn't cause any outright errors, but it doesn't seem
+ to conform to the RFCs and could cause (non-infinite) feedback loops in some
+ scenarios (usually those involving Paramiko on both ends).
- :support:`1292 backported` Backport changes from :issue:`979` (added in
Paramiko 2.3) to Paramiko 2.0-2.2, using duck-typing to preserve backwards
compatibility. This allows these older versions to use newer Cryptography
diff --git a/tests/test_transport.py b/tests/test_transport.py
index 13fb302e..3ea2cb26 100644
--- a/tests/test_transport.py
+++ b/tests/test_transport.py
@@ -30,6 +30,7 @@ import threading
import random
from hashlib import sha1
import unittest
+from mock import Mock
from paramiko import (
Transport,
@@ -47,6 +48,7 @@ from paramiko import OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
from paramiko.common import (
MSG_KEXINIT,
cMSG_CHANNEL_WINDOW_ADJUST,
+ cMSG_UNIMPLEMENTED,
MIN_PACKET_SIZE,
MIN_WINDOW_SIZE,
MAX_WINDOW_SIZE,
@@ -1027,3 +1029,26 @@ class TransportTest(unittest.TestCase):
assert "forwarding request denied" in str(e)
else:
assert False, "Did not raise SSHException!"
+
+ def _send_unimplemented(self, server_is_sender):
+ self.setup_test_server()
+ sender, recipient = self.tc, self.ts
+ if server_is_sender:
+ sender, recipient = self.ts, self.tc
+ recipient._send_message = Mock()
+ msg = Message()
+ msg.add_byte(cMSG_UNIMPLEMENTED)
+ sender._send_message(msg)
+ # TODO: I hate this but I literally don't see a good way to know when
+ # the recipient has received the sender's message (there are no
+ # existing threading events in play that work for this), esp in this
+ # case where we don't WANT a response (as otherwise we could
+ # potentially try blocking on the sender's receipt of a reply...maybe).
+ time.sleep(0.1)
+ assert not recipient._send_message.called
+
+ def test_server_does_not_respond_to_MSG_UNIMPLEMENTED(self):
+ self._send_unimplemented(server_is_sender=False)
+
+ def test_client_does_not_respond_to_MSG_UNIMPLEMENTED(self):
+ self._send_unimplemented(server_is_sender=True)