Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion src/rpcserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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() );
}
} );
}

Expand Down
1 change: 1 addition & 0 deletions src/rpcserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class CRpcServer : public QObject
// A map from method name to handler functions
QMap<QString, CRpcHandler> mapMethodHandlers;
QMap<QTcpSocket*, bool> isAuthenticated;
QMap<QTcpSocket*, bool> isDiscardingLine;
QVector<QTcpSocket*> vecClients;

void HandleApiAuth ( QTcpSocket* pSocket, const QJsonObject& params, QJsonObject& response );
Expand Down
16 changes: 15 additions & 1 deletion src/serverrpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
} );

Expand Down
Loading