Merge pull request #2031 from rposky/ssh-disconnect-loop

Prevent loops during disconnect
This commit is contained in:
terrafrost 2024-08-19 06:06:49 -05:00 committed by GitHub
commit dd2d8665f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 50 additions and 1 deletions

View File

@ -104,6 +104,7 @@ class SSH2
const MASK_LOGIN_REQ = 0x00000004; const MASK_LOGIN_REQ = 0x00000004;
const MASK_LOGIN = 0x00000008; const MASK_LOGIN = 0x00000008;
const MASK_SHELL = 0x00000010; const MASK_SHELL = 0x00000010;
const MASK_DISCONNECT = 0x00000020;
/* /*
* Channel constants * Channel constants
@ -4608,7 +4609,12 @@ class SSH2
*/ */
protected function disconnect_helper($reason) 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, '', ''); $data = Strings::packSSH2('CNss', NET_SSH2_MSG_DISCONNECT, $reason, '', '');
try { try {
$this->send_binary_packet($data); $this->send_binary_packet($data);

View File

@ -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 * @dataProvider formatLogDataProvider
* @requires PHPUnit < 10 * @requires PHPUnit < 10
@ -384,6 +402,31 @@ class SSH2UnitTest extends PhpseclibTestCase
self::callFunc($ssh, 'send_channel_packet', [1, 'hello world']); 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 * @return SSH2
*/ */