From c82594e222e591c32f7300b226a6db85da4d4f16 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 14 Aug 2024 10:32:36 -0400 Subject: [PATCH] Add DISCONNECT bitmap mask and use to prevent looping potential while disconnecting. Do not attempt disconnect packet send on closed socket. --- phpseclib/Net/SSH2.php | 8 +++++- tests/Unit/Net/SSH2UnitTest.php | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index ede87433..5387f60b 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -104,6 +104,7 @@ class SSH2 const MASK_LOGIN_REQ = 0x00000004; const MASK_LOGIN = 0x00000008; const MASK_SHELL = 0x00000010; + const MASK_DISCONNECT = 0x00000020; /* * Channel constants @@ -4593,7 +4594,12 @@ class SSH2 */ protected function disconnect_helper($reason) { - if ($this->bitmap & self::MASK_CONNECTED) { + if ($this->bitmap & self::MASK_DISCONNECT) { + // Disregard subsequent disconnect requests + return false; + } + $this->bitmap |= self::MASK_DISCONNECT; + if ($this->isConnected()) { $data = Strings::packSSH2('CNss', NET_SSH2_MSG_DISCONNECT, $reason, '', ''); try { $this->send_binary_packet($data); diff --git a/tests/Unit/Net/SSH2UnitTest.php b/tests/Unit/Net/SSH2UnitTest.php index ac7a9142..36bc86bf 100644 --- a/tests/Unit/Net/SSH2UnitTest.php +++ b/tests/Unit/Net/SSH2UnitTest.php @@ -33,6 +33,24 @@ class SSH2UnitTest extends PhpseclibTestCase ]; } + /** + * @requires PHPUnit < 10 + * Verify that MASK_* constants remain distinct + */ + public function testBitmapMasks() + { + $reflection = new \ReflectionClass(SSH2::class); + $masks = array_filter($reflection->getConstants(), function ($k) { + return strpos($k, 'MASK_') === 0; + }, ARRAY_FILTER_USE_KEY); + $bitmap = 0; + foreach ($masks as $mask => $bit) { + $this->assertEquals(0, $bitmap & $bit, "Got unexpected mask {$mask}"); + $bitmap |= $bit; + $this->assertEquals($bit, $bitmap & $bit, "Absent expected mask {$mask}"); + } + } + /** * @dataProvider formatLogDataProvider * @requires PHPUnit < 10 @@ -384,6 +402,31 @@ class SSH2UnitTest extends PhpseclibTestCase self::callFunc($ssh, 'send_channel_packet', [1, 'hello world']); } + /** + * @requires PHPUnit < 10 + */ + public function testDisconnectHelper() + { + $ssh = $this->getMockBuilder('phpseclib3\Net\SSH2') + ->disableOriginalConstructor() + ->setMethods(['__destruct', 'isConnected', 'send_binary_packet']) + ->getMock(); + $ssh->expects($this->once()) + ->method('isConnected') + ->willReturn(true); + $ssh->expects($this->once()) + ->method('send_binary_packet') + ->with($this->isType('string')) + ->willReturnCallback(function () use ($ssh) { + self::callFunc($ssh, 'disconnect_helper', [1]); + throw new \Exception('catch me'); + }); + + $this->assertEquals(0, self::getVar($ssh, 'bitmap')); + self::callFunc($ssh, 'disconnect_helper', [1]); + $this->assertEquals(0, self::getVar($ssh, 'bitmap')); + } + /** * @return SSH2 */