Subject: fix for vulnerability CVE-2012-0049 for OpenTTD 0.6.0 - 0.6.3 (Denial of service (server) via slow read attack) From: OpenTTD developer team Origin: backport, https://github.com/OpenTTD/OpenTTD/commit/bddfcae Bug: https://github.com/OpenTTD/OpenTTD/issues/4955 Using a slow read type attack it is possible to prevent anyone from joining a server with virtually no resources. Once downloading the map no other downloads of the map can start, so downloading really slowly will prevent others from joining. This can be further aggravated by the pause-on-join setting in which case the game is paused and the players cannot continue the game during such an attack. This attack requires that the user is not banned and passes the authorization to the server, although for many servers there is no server password and thus authorization is easy. A similar attack can be done when performing the attack during the authorization phase itself, however you will not block anyone else from joining, unless you use connection multiple times until the connection limit is reached, or stop the continuation of the game of the already joined players. This attack requires the user to be merely not banned. Note that versions before 0.6.0 are vulnerable as well. However, these versions are over five years old and not supported anymore. Therefore no patches for earlier versions are provided. Before 0.3.5 it is not possible to exploit this bug via the internet as multiplayer over internet did not exist yet. The provided patch is a simplification of the fix in 1.1.5 because that version slightly changes the network protocol to tell people they got kicked due to the (password) timeout. The attached patch does not change network compatibility. The fix in trunk does change network compatibility. Index: src/settings.cpp =================================================================== --- src/settings.cpp (revision 23768) +++ src/settings.cpp (working copy) @@ -1318,6 +1318,8 @@ SDTG_VAR("sync_freq", SLE_UINT16,C|S,0, _network_sync_freq, 100, 0, 100, 0, STR_NULL, NULL), SDTG_VAR("frame_freq", SLE_UINT8,C|S,0, _network_frame_freq, 0, 0, 100, 0, STR_NULL, NULL), SDTG_VAR("max_join_time", SLE_UINT16, S, 0, _network_max_join_time, 500, 0, 32000, 0, STR_NULL, NULL), + SDTG_VAR("max_download_time", SLE_UINT16, S,NO, _network_max_download_time, 1000, 0, 32000, 0, STR_NULL, NULL), + SDTG_VAR("max_password_time", SLE_UINT16, S,NO, _network_max_password_time, 2000, 0, 32000, 0, STR_NULL, NULL), SDTG_BOOL("pause_on_join", S, 0, _network_pause_on_join, true, STR_NULL, NULL), SDTG_STR("server_bind_ip", SLE_STRB, S, 0, _network_server_bind_ip_host, "0.0.0.0", STR_NULL, NULL), SDTG_VAR("server_port", SLE_UINT16, S, 0, _network_server_port, NETWORK_DEFAULT_PORT, 0, 65535, 0, STR_NULL, NULL), Index: src/network/network_server.cpp =================================================================== --- src/network/network_server.cpp (revision 23768) +++ src/network/network_server.cpp (working copy) @@ -1584,18 +1584,43 @@ } else { cs->lag_test = 0; } - } else if (cs->status == STATUS_PRE_ACTIVE) { + } else if (cs->status == STATUS_DONE_MAP || + cs->status == STATUS_PRE_ACTIVE) { + /* The map has been sent, so this is for loading the map and syncing up. */ int lag = NetworkCalculateLag(cs); if (lag > _network_max_join_time) { IConsolePrintF(_icolour_err,"Client #%d is dropped because it took longer than %d ticks for him to join", cs->index, _network_max_join_time); NetworkCloseClient(cs); } - } else if (cs->status == STATUS_INACTIVE) { + } else if (cs->status == STATUS_INACTIVE || + cs->status == STATUS_AUTH) { + /* NewGRF check and authorized states should be handled almost instantly. + * So give them some lee-way, likewise for the query with inactive. */ int lag = NetworkCalculateLag(cs); if (lag > 4 * DAY_TICKS) { IConsolePrintF(_icolour_err,"Client #%d is dropped because it took longer than %d ticks to start the joining process", cs->index, 4 * DAY_TICKS); NetworkCloseClient(cs); } + } else if (cs->status == STATUS_MAP) { + /* Downloading the map... this is the amount of time since starting the saving. */ + int lag = NetworkCalculateLag(cs); + if (lag > _network_max_download_time) { + IConsolePrintF(_icolour_err,"Client #%d is dropped because it took longer than %d ticks for him to download the map", cs->index, _network_max_download_time); + NetworkCloseClient(cs); + } + } else if (cs->status == STATUS_AUTHORIZING) { + /* Waiting for the password. */ + int lag = NetworkCalculateLag(cs); + if (lag > _network_max_password_time) { + IConsolePrintF(_icolour_err,"Client #%d is dropped because it took longer than %d ticks to enter the password", cs->index, _network_max_password_time); + NetworkCloseClient(cs); + } + } else if (cs->status == STATUS_MAP_WAIT) { + /* This is an internal state where we do not wait + * on the client to move to a different state. */ + } else { + /* Bad server/code. */ + NOT_REACHED(); } if (cs->status >= STATUS_PRE_ACTIVE) { Index: src/network/network_internal.h =================================================================== --- src/network/network_internal.h (revision 23768) +++ src/network/network_internal.h (working copy) @@ -140,6 +140,8 @@ VARDEF char _network_default_company_pass[NETWORK_PASSWORD_LENGTH]; VARDEF uint16 _network_max_join_time; ///< Time a client can max take to join +VARDEF uint16 _network_max_download_time; ///< maximum amount of time, in game ticks, a client may take to download the map +VARDEF uint16 _network_max_password_time; ///< maximum amount of time, in game ticks, a client may take to enter the password VARDEF bool _network_pause_on_join; ///< Pause the game when a client tries to join (more chance of succeeding join) VARDEF uint16 _redirect_console_to_client;