Skip to content

Commit

Permalink
Fix a pseudo-bug re: responding to MSG_UNIMPLEMENTED w/ itself
Browse files Browse the repository at this point in the history
  • Loading branch information
bitprophet committed Sep 19, 2018
1 parent 0b2e154 commit 852176d
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 5 deletions.
1 change: 1 addition & 0 deletions dev-requirements.txt
Expand Up @@ -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!
Expand Down
23 changes: 18 additions & 5 deletions paramiko/transport.py
Expand Up @@ -81,6 +81,8 @@
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
Expand Down Expand Up @@ -1958,11 +1960,22 @@ def run(self):
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))
Expand Down
5 changes: 5 additions & 0 deletions sites/www/changelog.rst
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions tests/test_transport.py
Expand Up @@ -30,6 +30,7 @@
import random
from hashlib import sha1
import unittest
from mock import Mock

from paramiko import (
Transport,
Expand All @@ -47,6 +48,7 @@
from paramiko.common import (
MSG_KEXINIT,
cMSG_CHANNEL_WINDOW_ADJUST,
cMSG_UNIMPLEMENTED,
MIN_PACKET_SIZE,
MIN_WINDOW_SIZE,
MAX_WINDOW_SIZE,
Expand Down Expand Up @@ -1027,3 +1029,26 @@ def test_server_rejects_port_forward_without_auth(self):
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)

0 comments on commit 852176d

Please sign in to comment.