diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 2c05c2f12d..49aed8107c 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -110,6 +110,11 @@ QJsonObject CRpcServer::CreateJsonRpcErrorReply ( int code, QString message ) return object; } +// Maximum size of a single JSON-RPC request line. The largest legitimate request is a +// MAX_LEN_CHAT_TEXT (1600) character welcome or chat message, 9698 bytes on the wire +// when every character is JSON-escaped as \uXXXX; 16 KiB leaves 1.7x that. +static constexpr int MAX_JSON_RPC_REQUEST_BYTES = 16 * 1024; + void CRpcServer::OnNewConnection() { QTcpSocket* pSocket = pTransportServer->nextPendingConnection(); @@ -120,16 +125,36 @@ void CRpcServer::OnNewConnection() qDebug() << "- JSON-RPC: received connection from:" << pSocket->peerAddress().toString(); vecClients.append ( pSocket ); - isAuthenticated[pSocket] = false; + isAuthenticated[pSocket] = false; + isDiscardingLine[pSocket] = false; + + // Bound the per-connection read buffer so unterminated input cannot exhaust memory. + pSocket->setReadBufferSize ( MAX_JSON_RPC_REQUEST_BYTES ); connect ( pSocket, &QTcpSocket::disconnected, [this, pSocket]() { qDebug() << "- JSON-RPC: connection from:" << pSocket->peerAddress().toString() << "closed"; vecClients.removeAll ( pSocket ); isAuthenticated.remove ( pSocket ); + isDiscardingLine.remove ( pSocket ); pSocket->deleteLater(); } ); connect ( pSocket, &QTcpSocket::readyRead, [this, pSocket]() { + // An oversized request was already answered with an error; the rest of its line + // is discarded here so that the connection, and the authentication bound to it, + // survive and the next request is read normally. + if ( isDiscardingLine[pSocket] ) + { + const QByteArray sPending = pSocket->peek ( pSocket->bytesAvailable() ); + const int iEndOfLine = sPending.indexOf ( '\n' ); + pSocket->read ( iEndOfLine < 0 ? sPending.size() : iEndOfLine + 1 ); + if ( iEndOfLine < 0 ) + { + return; + } + isDiscardingLine[pSocket] = false; + } + while ( pSocket->canReadLine() ) { QByteArray line = pSocket->readLine(); @@ -197,6 +222,18 @@ void CRpcServer::OnNewConnection() pSocket->disconnectFromHost(); return; } + + // A full buffer with no complete line is an oversized or unterminated request: + // answer it, then drop the bytes instead of holding them. + if ( !pSocket->canReadLine() && pSocket->bytesAvailable() >= MAX_JSON_RPC_REQUEST_BYTES ) + { + Send ( pSocket, + QJsonDocument ( CreateJsonRpcErrorReply ( + iErrParseError, + QString ( "Parse error: Request exceeds maximum size of %1 bytes" ).arg ( MAX_JSON_RPC_REQUEST_BYTES ) ) ) ); + isDiscardingLine[pSocket] = true; + pSocket->read ( pSocket->bytesAvailable() ); + } } ); } diff --git a/src/rpcserver.h b/src/rpcserver.h index cf9da2706f..0803099a56 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -94,6 +94,7 @@ class CRpcServer : public QObject // A map from method name to handler functions QMap mapMethodHandlers; QMap isAuthenticated; + QMap isDiscardingLine; QVector vecClients; void HandleApiAuth ( QTcpSocket* pSocket, const QJsonObject& params, QJsonObject& response ); diff --git a/src/serverrpc.cpp b/src/serverrpc.cpp index 05bb54be62..015f3a85f9 100644 --- a/src/serverrpc.cpp +++ b/src/serverrpc.cpp @@ -319,7 +319,21 @@ CServerRpc::CServerRpc ( CServer* pServer, CRpcServer* pRpcServer, QObject* pare return; } - pServer->SetWelcomeMessage ( jsonWelcomeMessage.toString() ); + // Check the decoded string, not the request bytes: \uXXXX escapes and multi-byte + // UTF-8 both make the encoded form longer than the string it produces. The bound is + // MAX_LEN_CHAT_TEXT because that is what CServer::SetWelcomeMessage truncates to; + // accepting more here would report success and then silently discard the excess. + const QString strWelcomeMessage = jsonWelcomeMessage.toString(); + + if ( strWelcomeMessage.length() > MAX_LEN_CHAT_TEXT ) + { + response["error"] = CRpcServer::CreateJsonRpcError ( + CRpcServer::iErrInvalidParams, + QString ( "Invalid params: welcomeMessage exceeds maximum length of %1 characters" ).arg ( MAX_LEN_CHAT_TEXT ) ); + return; + } + + pServer->SetWelcomeMessage ( strWelcomeMessage ); response["result"] = "ok"; } );