Summary
AgentServer.handle_new_agent uses await reader.read(ProtocolConfig.BUFFER_SIZE) with no timeout. Because max_connections is typically 2 (one attacker, one defender), an attacker who opens that many idle TCP connections blocks every legitimate agent from ever connecting — permanently, at zero cost.
Vulnerable Code
File: netsecgame/game/agent_server.py, lines 57–69
if self.current_connections < self.max_connections:
self.current_connections += 1
...
while True:
# NO timeout — blocks indefinitely if client sends nothing
data = await reader.read(ProtocolConfig.BUFFER_SIZE)
There is no asyncio.wait_for, no keepalive, and no idle-connection reaper. A client that connects but never sends data holds its slot until it explicitly closes the TCP connection.
Steps to Reproduce
import socket, time
HOST, PORT = "localhost", 5000 # game port
MAX_CONNECTIONS = 2 # typical value for 1 attacker + 1 defender
slots = []
for _ in range(MAX_CONNECTIONS):
s = socket.create_connection((HOST, PORT))
slots.append(s)
# do NOT send any data — server coroutine blocks at reader.read() forever
print("All connection slots exhausted. Legitimate agents cannot connect.")
time.sleep(99999) # hold slots open indefinitely
After this, any legitimate agent attempting to connect receives the log message "Max connections reached. Rejecting new connection" and is immediately dropped.
Expected Behaviour
The server should enforce an idle/read timeout so that connections that do not send data within a configurable window are closed and their slots released.
Actual Behaviour
Idle TCP connections are held open indefinitely. Once max_connections slots are full, no legitimate agent can join the game.
Impact
- Complete availability loss: with
max_connections = 2, two idle TCP sockets from a single attacker machine are enough to permanently prevent any training run from starting.
- No authentication is required to open those sockets (see related issue), so the attack is reachable from any host on the network.
- The
max(0, self.current_connections - 1) guard in the finally block (line 110) only fires on disconnect, so slots remain consumed for as long as the attacker maintains the connections.
Recommendation
Wrap the read call with an asyncio timeout:
data = await asyncio.wait_for(
reader.read(ProtocolConfig.BUFFER_SIZE),
timeout=IDLE_TIMEOUT_SECONDS
)
Catch asyncio.TimeoutError to close the connection cleanly. An appropriate idle timeout (e.g., 30–60 s) should be made configurable.
Summary
AgentServer.handle_new_agentusesawait reader.read(ProtocolConfig.BUFFER_SIZE)with no timeout. Becausemax_connectionsis typically 2 (one attacker, one defender), an attacker who opens that many idle TCP connections blocks every legitimate agent from ever connecting — permanently, at zero cost.Vulnerable Code
File:
netsecgame/game/agent_server.py, lines 57–69There is no
asyncio.wait_for, no keepalive, and no idle-connection reaper. A client that connects but never sends data holds its slot until it explicitly closes the TCP connection.Steps to Reproduce
After this, any legitimate agent attempting to connect receives the log message
"Max connections reached. Rejecting new connection"and is immediately dropped.Expected Behaviour
The server should enforce an idle/read timeout so that connections that do not send data within a configurable window are closed and their slots released.
Actual Behaviour
Idle TCP connections are held open indefinitely. Once
max_connectionsslots are full, no legitimate agent can join the game.Impact
max_connections = 2, two idle TCP sockets from a single attacker machine are enough to permanently prevent any training run from starting.max(0, self.current_connections - 1)guard in thefinallyblock (line 110) only fires on disconnect, so slots remain consumed for as long as the attacker maintains the connections.Recommendation
Wrap the read call with an asyncio timeout:
Catch
asyncio.TimeoutErrorto close the connection cleanly. An appropriate idle timeout (e.g., 30–60 s) should be made configurable.