Fix #11016: Defer deletion of client and server game socket handlers

This fixes various use after free scenarios in error handling paths
This commit is contained in:
Jonathan G Rennison 2023-06-16 20:54:04 +01:00 committed by PeterN
parent 19ae88fb63
commit 4f6d75f97d
5 changed files with 38 additions and 7 deletions

View File

@ -20,6 +20,8 @@
#include "../../safeguards.h" #include "../../safeguards.h"
static std::vector<std::unique_ptr<NetworkGameSocketHandler>> _deferred_deletions;
/** /**
* Create a new socket for the game connection. * Create a new socket for the game connection.
* @param s The socket to connect with. * @param s The socket to connect with.
@ -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_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_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); } 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();
}

View File

@ -154,7 +154,8 @@ public:
class NetworkGameSocketHandler : public NetworkTCPSocketHandler { class NetworkGameSocketHandler : public NetworkTCPSocketHandler {
/* TODO: rewrite into a proper class */ /* TODO: rewrite into a proper class */
private: 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: protected:
NetworkRecvStatus ReceiveInvalidPacket(PacketGameType type); NetworkRecvStatus ReceiveInvalidPacket(PacketGameType type);
@ -543,6 +544,11 @@ public:
const char *ReceiveCommand(Packet *p, CommandPacket *cp); const char *ReceiveCommand(Packet *p, CommandPacket *cp);
void SendCommand(Packet *p, const 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 */ #endif /* NETWORK_CORE_TCP_GAME_H */

View File

@ -595,6 +595,7 @@ void NetworkClose(bool close_admins)
_network_coordinator_client.CloseAllConnections(); _network_coordinator_client.CloseAllConnections();
} }
NetworkGameSocketHandler::ProcessDeferredDeletions();
TCPConnecter::KillAll(); TCPConnecter::KillAll();
@ -992,12 +993,15 @@ void NetworkUpdateServerGameType()
*/ */
static bool NetworkReceive() static bool NetworkReceive()
{ {
bool result;
if (_network_server) { if (_network_server) {
ServerNetworkAdminSocketHandler::Receive(); ServerNetworkAdminSocketHandler::Receive();
return ServerNetworkGameSocketHandler::Receive(); result = ServerNetworkGameSocketHandler::Receive();
} else { } else {
return ClientNetworkGameSocketHandler::Receive(); result = ClientNetworkGameSocketHandler::Receive();
} }
NetworkGameSocketHandler::ProcessDeferredDeletions();
return result;
} }
/* This sends all buffered commands (if possible) */ /* This sends all buffered commands (if possible) */
@ -1009,6 +1013,7 @@ static void NetworkSend()
} else { } else {
ClientNetworkGameSocketHandler::Send(); ClientNetworkGameSocketHandler::Send();
} }
NetworkGameSocketHandler::ProcessDeferredDeletions();
} }
/** /**
@ -1023,6 +1028,7 @@ void NetworkBackgroundLoop()
TCPConnecter::CheckCallbacks(); TCPConnecter::CheckCallbacks();
NetworkHTTPSocketHandler::HTTPReceive(); NetworkHTTPSocketHandler::HTTPReceive();
QueryNetworkGameSocketHandler::SendReceive(); QueryNetworkGameSocketHandler::SendReceive();
NetworkGameSocketHandler::ProcessDeferredDeletions();
NetworkBackgroundUDPLoop(); NetworkBackgroundUDPLoop();
} }

View File

@ -160,6 +160,8 @@ ClientNetworkGameSocketHandler::~ClientNetworkGameSocketHandler()
NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvStatus status) NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvStatus status)
{ {
assert(status != NETWORK_RECV_STATUS_OKAY); assert(status != NETWORK_RECV_STATUS_OKAY);
if (this->IsPendingDeletion()) return status;
assert(this->sock != INVALID_SOCKET); assert(this->sock != INVALID_SOCKET);
if (!this->HasClientQuit()) { if (!this->HasClientQuit()) {
@ -174,7 +176,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
CSleep(3 * MILLISECONDS_PER_TICK); CSleep(3 * MILLISECONDS_PER_TICK);
} }
delete this; this->DeferDeletion();
return status; return status;
} }
@ -185,6 +187,8 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
*/ */
void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res) void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res)
{ {
if (this->IsPendingDeletion()) return;
/* First, send a CLIENT_ERROR to the server, so it knows we are /* First, send a CLIENT_ERROR to the server, so it knows we are
* disconnected (and why!) */ * disconnected (and why!) */
NetworkErrorCode errorno; NetworkErrorCode errorno;

View File

@ -224,6 +224,8 @@ ServerNetworkGameSocketHandler::ServerNetworkGameSocketHandler(SOCKET s) : Netwo
*/ */
ServerNetworkGameSocketHandler::~ServerNetworkGameSocketHandler() ServerNetworkGameSocketHandler::~ServerNetworkGameSocketHandler()
{ {
delete this->GetInfo();
if (_redirect_console_to_client == this->client_id) _redirect_console_to_client = INVALID_CLIENT_ID; if (_redirect_console_to_client == this->client_id) _redirect_console_to_client = INVALID_CLIENT_ID;
OrderBackup::ResetUser(this->client_id); OrderBackup::ResetUser(this->client_id);
@ -256,7 +258,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
* connection. This handles that case gracefully without having to make * connection. This handles that case gracefully without having to make
* that code any more complex or more aware of the validity of the socket. * 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) { 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... */ /* We did not receive a leave message from this client... */
@ -292,8 +294,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
this->SendPackets(true); this->SendPackets(true);
delete this->GetInfo(); this->DeferDeletion();
delete this;
InvalidateWindowData(WC_CLIENT_LIST, 0); InvalidateWindowData(WC_CLIENT_LIST, 0);