Skip to content

Commit

Permalink
Fix OpenTTD#11016: Defer deletion of client and server game socket ha…
Browse files Browse the repository at this point in the history
…ndlers

This fixes various use after free scenarios in error handling paths
  • Loading branch information
JGRennison authored and shoter committed Jul 16, 2023
1 parent 399cbca commit 3ea8678
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 7 deletions.
14 changes: 14 additions & 0 deletions src/network/core/tcp_game.cpp
Expand Up @@ -20,6 +20,8 @@

#include "../../safeguards.h"

static std::vector<std::unique_ptr<NetworkGameSocketHandler>> _deferred_deletions;

/**
* Create a new socket for the game connection.
* @param s The socket to connect with.
Expand Down Expand Up @@ -198,3 +200,15 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_MOVE(Packet *p) { ret
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_MOVE(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_MOVE); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_COMPANY_UPDATE(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_COMPANY_UPDATE); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_CONFIG_UPDATE(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_CONFIG_UPDATE); }

void NetworkGameSocketHandler::DeferDeletion()
{
_deferred_deletions.emplace_back(this);
this->is_pending_deletion = true;
}

/* static */ void NetworkGameSocketHandler::ProcessDeferredDeletions()
{
/* Calls deleter on all items */
_deferred_deletions.clear();
}
8 changes: 7 additions & 1 deletion src/network/core/tcp_game.h
Expand Up @@ -154,7 +154,8 @@ class CommandQueue {
class NetworkGameSocketHandler : public NetworkTCPSocketHandler {
/* TODO: rewrite into a proper class */
private:
NetworkClientInfo *info; ///< Client info related to this socket
NetworkClientInfo *info; ///< Client info related to this socket
bool is_pending_deletion = false; ///< Whether this socket is pending deletion

protected:
NetworkRecvStatus ReceiveInvalidPacket(PacketGameType type);
Expand Down Expand Up @@ -543,6 +544,11 @@ class NetworkGameSocketHandler : public NetworkTCPSocketHandler {

const char *ReceiveCommand(Packet *p, CommandPacket *cp);
void SendCommand(Packet *p, const CommandPacket *cp);

bool IsPendingDeletion() const { return this->is_pending_deletion; }

void DeferDeletion();
static void ProcessDeferredDeletions();
};

#endif /* NETWORK_CORE_TCP_GAME_H */
10 changes: 8 additions & 2 deletions src/network/network.cpp
Expand Up @@ -595,6 +595,7 @@ void NetworkClose(bool close_admins)

_network_coordinator_client.CloseAllConnections();
}
NetworkGameSocketHandler::ProcessDeferredDeletions();

TCPConnecter::KillAll();

Expand Down Expand Up @@ -992,12 +993,15 @@ void NetworkUpdateServerGameType()
*/
static bool NetworkReceive()
{
bool result;
if (_network_server) {
ServerNetworkAdminSocketHandler::Receive();
return ServerNetworkGameSocketHandler::Receive();
result = ServerNetworkGameSocketHandler::Receive();
} else {
return ClientNetworkGameSocketHandler::Receive();
result = ClientNetworkGameSocketHandler::Receive();
}
NetworkGameSocketHandler::ProcessDeferredDeletions();
return result;
}

/* This sends all buffered commands (if possible) */
Expand All @@ -1009,6 +1013,7 @@ static void NetworkSend()
} else {
ClientNetworkGameSocketHandler::Send();
}
NetworkGameSocketHandler::ProcessDeferredDeletions();
}

/**
Expand All @@ -1023,6 +1028,7 @@ void NetworkBackgroundLoop()
TCPConnecter::CheckCallbacks();
NetworkHTTPSocketHandler::HTTPReceive();
QueryNetworkGameSocketHandler::SendReceive();
NetworkGameSocketHandler::ProcessDeferredDeletions();

NetworkBackgroundUDPLoop();
}
Expand Down
6 changes: 5 additions & 1 deletion src/network/network_client.cpp
Expand Up @@ -160,6 +160,8 @@ ClientNetworkGameSocketHandler::~ClientNetworkGameSocketHandler()
NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvStatus status)
{
assert(status != NETWORK_RECV_STATUS_OKAY);
if (this->IsPendingDeletion()) return status;

assert(this->sock != INVALID_SOCKET);

if (!this->HasClientQuit()) {
Expand All @@ -174,7 +176,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
CSleep(3 * MILLISECONDS_PER_TICK);
}

delete this;
this->DeferDeletion();

return status;
}
Expand All @@ -185,6 +187,8 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
*/
void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res)
{
if (this->IsPendingDeletion()) return;

/* First, send a CLIENT_ERROR to the server, so it knows we are
* disconnected (and why!) */
NetworkErrorCode errorno;
Expand Down
7 changes: 4 additions & 3 deletions src/network/network_server.cpp
Expand Up @@ -224,6 +224,8 @@ ServerNetworkGameSocketHandler::ServerNetworkGameSocketHandler(SOCKET s) : Netwo
*/
ServerNetworkGameSocketHandler::~ServerNetworkGameSocketHandler()
{
delete this->GetInfo();

if (_redirect_console_to_client == this->client_id) _redirect_console_to_client = INVALID_CLIENT_ID;
OrderBackup::ResetUser(this->client_id);

Expand Down Expand Up @@ -256,7 +258,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
* connection. This handles that case gracefully without having to make
* that code any more complex or more aware of the validity of the socket.
*/
if (this->sock == INVALID_SOCKET) return status;
if (this->IsPendingDeletion() || this->sock == INVALID_SOCKET) return status;

if (status != NETWORK_RECV_STATUS_CLIENT_QUIT && status != NETWORK_RECV_STATUS_SERVER_ERROR && !this->HasClientQuit() && this->status >= STATUS_AUTHORIZED) {
/* We did not receive a leave message from this client... */
Expand Down Expand Up @@ -292,8 +294,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::CloseConnection(NetworkRecvSta

this->SendPackets(true);

delete this->GetInfo();
delete this;
this->DeferDeletion();

InvalidateWindowData(WC_CLIENT_LIST, 0);

Expand Down

0 comments on commit 3ea8678

Please sign in to comment.