From 4a79bbd984bbbd3d5fd17b3a5c14062ac9066787 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 7 Oct 2020 09:09:37 +0100 Subject: [PATCH] Added additional checking if response length is beyond a reasonable limit (10mb) --- .../Exception/FailedToSendCommand.php | 24 +++++++++++++++ src/Connector/SocketConnector.php | 29 ++++++++++++++++--- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/Connector/Exception/FailedToSendCommand.php b/src/Connector/Exception/FailedToSendCommand.php index b48463a1..6623ff5e 100644 --- a/src/Connector/Exception/FailedToSendCommand.php +++ b/src/Connector/Exception/FailedToSendCommand.php @@ -65,6 +65,30 @@ public static function fromEmptyResponseSize(Command $attemptedCommand, Connecti )); } + public static function fromFailedResponseUnpack(Command $attemptedCommand, ConnectionAddress $connectionAddress) : self + { + return new self(sprintf( + 'Response length could not be unpacked for %s (maybe invalid format?). Address was: %s', + get_class($attemptedCommand), + $connectionAddress->toString() + )); + } + + public static function fromTooLargeResponseLength( + int $responseLengthReturned, + int $responseLengthLimit, + Command $attemptedCommand, + ConnectionAddress $connectionAddress + ) : self { + return new self(sprintf( + 'Response length returned (%d) exceeded our limit for reading (%d) for %s. Address was: %s', + $responseLengthReturned, + $responseLengthLimit, + get_class($attemptedCommand), + $connectionAddress->toString() + )); + } + /** @param resource $socketResource */ public static function readingResponseContentFromSocket(Command $attemptedCommand, $socketResource, ConnectionAddress $connectionAddress) : self { diff --git a/src/Connector/SocketConnector.php b/src/Connector/SocketConnector.php index 68610fde..08eb49a5 100644 --- a/src/Connector/SocketConnector.php +++ b/src/Connector/SocketConnector.php @@ -14,6 +14,8 @@ use const E_STRICT; use const E_WARNING; use const SOCK_STREAM; +use function array_key_exists; +use function is_array; use function json_encode; use function pack; use function register_shutdown_function; @@ -32,6 +34,8 @@ /** @internal */ final class SocketConnector implements Connector { + private const MAXIMUM_RESPONSE_LENGTH_TO_READ = 10000000; + /** @var resource */ private $socket; @@ -139,17 +143,34 @@ public function sendCommand(Command $message) : string } // Read the response back and drop it. Needed for socket liveness - $responseLength = @socket_read($this->socket, 4); + $responseLengthPacked = @socket_read($this->socket, 4); - if ($responseLength === false) { + if ($responseLengthPacked === false) { throw Exception\FailedToSendCommand::readingResponseSizeFromSocket($message, $this->socket, $this->connectionAddress); } - if ($responseLength === '') { + if ($responseLengthPacked === '') { throw Exception\FailedToSendCommand::fromEmptyResponseSize($message, $this->connectionAddress); } - $dataRead = @socket_read($this->socket, unpack('N', $responseLength)[1]); + $responseLengthUnpacked = unpack('Nlen', $responseLengthPacked); + + if (! is_array($responseLengthUnpacked) || ! array_key_exists('len', $responseLengthUnpacked)) { + throw Exception\FailedToSendCommand::fromFailedResponseUnpack($message, $this->connectionAddress); + } + + $responseLength = (int) $responseLengthUnpacked['len']; + + if ($responseLength > self::MAXIMUM_RESPONSE_LENGTH_TO_READ) { + throw Exception\FailedToSendCommand::fromTooLargeResponseLength( + $responseLength, + self::MAXIMUM_RESPONSE_LENGTH_TO_READ, + $message, + $this->connectionAddress + ); + } + + $dataRead = @socket_read($this->socket, $responseLength); if ($dataRead === false) { throw Exception\FailedToSendCommand::readingResponseContentFromSocket($message, $this->socket, $this->connectionAddress);