From de27cb8a2f2dd16b3f97200a0c1fa20681f3fc71 Mon Sep 17 00:00:00 2001 From: Asher Baker Date: Thu, 9 Aug 2012 01:54:43 +0100 Subject: [PATCH] Added system to block malware or illegal plugins (bug 5289, r=dvander). --- configs/core.cfg | 10 + core/PluginSys.cpp | 69 +++- core/PluginSys.h | 5 + gamedata/core.games/blacklist.plugins.txt | 16 + gamedata/core.games/master.games.txt | 3 + public/sourcepawn/sp_vm_api.h | 16 +- sourcepawn/jit/AMBuilder | 1 + sourcepawn/jit/BaseRuntime.cpp | 25 ++ sourcepawn/jit/BaseRuntime.h | 5 + sourcepawn/jit/md5/md5.cpp | 476 ++++++++++++++++++++++ sourcepawn/jit/md5/md5.h | 106 +++++ 11 files changed, 730 insertions(+), 2 deletions(-) create mode 100644 gamedata/core.games/blacklist.plugins.txt create mode 100644 sourcepawn/jit/md5/md5.cpp create mode 100644 sourcepawn/jit/md5/md5.h diff --git a/configs/core.cfg b/configs/core.cfg index e1d543e1..a8ab408a 100644 --- a/configs/core.cfg +++ b/configs/core.cfg @@ -115,4 +115,14 @@ * In general, this option should be set to "yes" to increase the security of your server. */ "SteamAuthstringValidation" "yes" + + /** + * Enables or disables whether SourceMod blocks known or potentially malicious plugins from loading. + * It is STRONGLY advised that this is left enabled, there have been cases in the past with plugins that + * allow anyone to delete files on the server, gain full rcon control, etc. + * + * "yes" - Block malware or illegal plugins from loading (default) + * "no" - Warn about malware or illegal plugins loading + */ + "BlockBadPlugins" "yes" } diff --git a/core/PluginSys.cpp b/core/PluginSys.cpp index 119cc87a..c8c621b1 100644 --- a/core/PluginSys.cpp +++ b/core/PluginSys.cpp @@ -789,6 +789,8 @@ CPluginManager::CPluginManager() m_AllPluginsLoaded = false; m_MyIdent = NULL; m_LoadingLocked = false; + + m_bBlockBadPlugins = true; } CPluginManager::~CPluginManager() @@ -1030,6 +1032,41 @@ LoadRes CPluginManager::_LoadPlugin(CPlugin **_plugin, const char *path, bool de } } } + + if (pPlugin->GetStatus() == Plugin_Created) + { + unsigned char *pCodeHash = pPlugin->m_pRuntime->GetCodeHash(); + + char codeHashBuf[40]; + UTIL_Format(codeHashBuf, 40, "plugin_"); + for (int i = 0; i < 16; i++) + UTIL_Format(codeHashBuf + 7 + (i * 2), 3, "%02x", pCodeHash[i]); + + const char *bulletinUrl = g_pGameConf->GetKeyValue(codeHashBuf); + if (bulletinUrl != NULL) + { + if (m_bBlockBadPlugins) + { + if (error) + { + if (bulletinUrl[0] != '\0') + { + UTIL_Format(error, maxlength, "Known malware detected and blocked. See %s for more info", bulletinUrl); + } else { + UTIL_Format(error, maxlength, "Possible malware or illegal plugin detected and blocked"); + } + } + pPlugin->m_status = Plugin_BadLoad; + } else { + if (bulletinUrl[0] != '\0') + { + g_Logger.LogMessage("%s: Known malware detected. See %s for more info, blocking disabled in core.cfg", pPlugin->GetFilename(), bulletinUrl); + } else { + g_Logger.LogMessage("%s: Possible malware or illegal plugin detected, blocking disabled in core.cfg", pPlugin->GetFilename()); + } + } + } + } LoadRes loadFailure = LoadRes_Failure; /* Get the status */ @@ -1129,7 +1166,7 @@ void CPluginManager::LoadAutoPlugin(const char *plugin) if ((res=_LoadPlugin(&pl, plugin, false, PluginType_MapUpdated, error, sizeof(error))) == LoadRes_Failure) { - g_Logger.LogError("[SM] Failed to load plugin \"%s\": %s", plugin, error); + g_Logger.LogError("[SM] Failed to load plugin \"%s\": %s.", plugin, error); pl->SetErrorState( pl->GetStatus() <= Plugin_Created ? Plugin_BadLoad : pl->GetStatus(), "%s", @@ -1889,6 +1926,27 @@ void CPluginManager::OnSourceModShutdown() g_ShareSys.DestroyIdentity(m_MyIdent); } +ConfigResult CPluginManager::OnSourceModConfigChanged(const char *key, + const char *value, + ConfigSource source, + char *error, + size_t maxlength) +{ + if (strcmp(key, "BlockBadPlugins") == 0) { + if (strcasecmp(value, "yes") == 0) + { + m_bBlockBadPlugins = true; + } else if (strcasecmp(value, "no") == 0) { + m_bBlockBadPlugins = false; + } else { + UTIL_Format(error, maxlength, "Invalid value: must be \"yes\" or \"no\""); + return ConfigResult_Reject; + } + return ConfigResult_Accept; + } + return ConfigResult_Ignore; +} + void CPluginManager::OnHandleDestroy(HandleType_t type, void *object) { /* We don't care about the internal object, actually */ @@ -2269,6 +2327,15 @@ void CPluginManager::OnRootConsoleCommand(const char *cmdname, const CCommand &c { g_RootMenu.ConsolePrint(" Timestamp: %s", pl->m_DateTime); } + + unsigned char *pCodeHash = pl->m_pRuntime->GetCodeHash(); + unsigned char *pDataHash = pl->m_pRuntime->GetDataHash(); + + char combinedHash[33]; + for (int i = 0; i < 16; i++) + UTIL_Format(combinedHash + (i * 2), 3, "%02x", pCodeHash[i] ^ pDataHash[i]); + + g_RootMenu.ConsolePrint(" Hash: %s", combinedHash); } else { diff --git a/core/PluginSys.h b/core/PluginSys.h index 3b10998e..4c9c547d 100644 --- a/core/PluginSys.h +++ b/core/PluginSys.h @@ -57,6 +57,7 @@ #include "convar_sm.h" #endif #include "ITranslator.h" +#include "IGameConfigs.h" #include "NativeOwner.h" #include "ShareSys.h" @@ -326,6 +327,7 @@ public: //IPluginManager public: //SMGlobalClass void OnSourceModAllInitialized(); void OnSourceModShutdown(); + ConfigResult OnSourceModConfigChanged(const char *key, const char *value, ConfigSource source, char *error, size_t maxlength); void OnSourceModMaxPlayersChanged(int newvalue); public: //IHandleTypeDispatch void OnHandleDestroy(HandleType_t type, void *object); @@ -470,6 +472,9 @@ private: List m_Natives; bool m_LoadingLocked; + + // Config + bool m_bBlockBadPlugins; }; extern CPluginManager g_PluginSys; diff --git a/gamedata/core.games/blacklist.plugins.txt b/gamedata/core.games/blacklist.plugins.txt new file mode 100644 index 00000000..b65a7454 --- /dev/null +++ b/gamedata/core.games/blacklist.plugins.txt @@ -0,0 +1,16 @@ +/** + * Do not edit this file. Any changes will be overwritten by the gamedata + * updater or by upgrading your SourceMod install. + * + * For more information, see http://wiki.alliedmods.net/Gamedata_Updating_(SourceMod) + */ + +"Games" +{ + "#default" + { + "Keys" + { + } + } +} diff --git a/gamedata/core.games/master.games.txt b/gamedata/core.games/master.games.txt index ecf2f93e..949a5540 100644 --- a/gamedata/core.games/master.games.txt +++ b/gamedata/core.games/master.games.txt @@ -65,4 +65,7 @@ "engine" "csgo" } + "blacklist.plugins.txt" + { + } } diff --git a/public/sourcepawn/sp_vm_api.h b/public/sourcepawn/sp_vm_api.h index 14303256..2f43b28c 100644 --- a/public/sourcepawn/sp_vm_api.h +++ b/public/sourcepawn/sp_vm_api.h @@ -40,7 +40,7 @@ /** SourcePawn Engine API Version */ #define SOURCEPAWN_ENGINE_API_VERSION 4 -#define SOURCEPAWN_ENGINE2_API_VERSION 3 +#define SOURCEPAWN_ENGINE2_API_VERSION 4 #if !defined SOURCEMOD_BUILD #define SOURCEMOD_BUILD @@ -479,6 +479,20 @@ namespace SourcePawn * @return Memory usage, in bytes. */ virtual size_t GetMemUsage() =0; + + /** + * @brief Returns the MD5 hash of the plugin's P-Code. + * + * @return 16-byte buffer with MD5 hash of the plugin's P-Code. + */ + virtual unsigned char *GetCodeHash() =0; + + /** + * @brief Returns the MD5 hash of the plugin's Data. + * + * @return 16-byte buffer with MD5 hash of the plugin's Data. + */ + virtual unsigned char *GetDataHash() =0; }; /** diff --git a/sourcepawn/jit/AMBuilder b/sourcepawn/jit/AMBuilder index e3ce4282..4ad25b1e 100644 --- a/sourcepawn/jit/AMBuilder +++ b/sourcepawn/jit/AMBuilder @@ -36,6 +36,7 @@ binary.AddSourceFiles('sourcepawn/jit', [ 'zlib/trees.c', 'zlib/uncompr.c', 'zlib/zutil.c', + 'md5/md5.cpp', '../../knight/shared/KeCodeAllocator.cpp' ]) SM.AutoVersion('sourcepawn/jit', binary) diff --git a/sourcepawn/jit/BaseRuntime.cpp b/sourcepawn/jit/BaseRuntime.cpp index 942eea28..677d6ffd 100644 --- a/sourcepawn/jit/BaseRuntime.cpp +++ b/sourcepawn/jit/BaseRuntime.cpp @@ -8,6 +8,8 @@ #include "sp_vm_basecontext.h" #include "engine2.h" +#include "md5/md5.h" + using namespace SourcePawn; BaseRuntime::BaseRuntime() : m_Debug(&m_plugin), m_pPlugin(&m_plugin), m_pCtx(NULL), @@ -18,6 +20,9 @@ m_PubFuncs(NULL), m_PubJitFuncs(NULL), m_pCo(NULL), m_CompSerial(0) m_FuncCache = NULL; m_MaxFuncs = 0; m_NumFuncs = 0; + + memset(m_CodeHash, 0, sizeof(m_CodeHash)); + memset(m_DataHash, 0, sizeof(m_DataHash)); } BaseRuntime::~BaseRuntime() @@ -227,6 +232,16 @@ int BaseRuntime::CreateFromMemory(sp_file_hdr_t *hdr, uint8_t *base) memset(m_PubJitFuncs, 0, sizeof(JitFunction *) * m_pPlugin->num_publics); } + MD5 md5_pcode; + md5_pcode.update(plugin->pcode, plugin->pcode_size); + md5_pcode.finalize(); + md5_pcode.raw_digest(m_CodeHash); + + MD5 md5_data; + md5_data.update(plugin->data, plugin->data_size); + md5_data.finalize(); + md5_data.raw_digest(m_DataHash); + m_pPlugin->profiler = g_engine2.GetProfiler(); m_pCtx = new BaseContext(this); m_pCo = g_Jit.StartCompilation(this); @@ -483,6 +498,16 @@ size_t BaseRuntime::GetMemUsage() return mem; } +unsigned char *BaseRuntime::GetCodeHash() +{ + return m_CodeHash; +} + +unsigned char *BaseRuntime::GetDataHash() +{ + return m_DataHash; +} + BaseContext *BaseRuntime::GetBaseContext() { return m_pCtx; diff --git a/sourcepawn/jit/BaseRuntime.h b/sourcepawn/jit/BaseRuntime.h index 2217db8f..6c9fecc7 100644 --- a/sourcepawn/jit/BaseRuntime.h +++ b/sourcepawn/jit/BaseRuntime.h @@ -48,6 +48,8 @@ public: virtual void SetPauseState(bool paused); virtual bool IsPaused(); virtual size_t GetMemUsage(); + virtual unsigned char *GetCodeHash(); + virtual unsigned char *GetDataHash(); JitFunction *GetJittedFunction(uint32_t idx); uint32_t AddJittedFunction(JitFunction *fn); public: @@ -65,6 +67,9 @@ public: JitFunction **m_PubJitFuncs; ICompilation *m_pCo; unsigned int m_CompSerial; + + unsigned char m_CodeHash[16]; + unsigned char m_DataHash[16]; }; #endif //_INCLUDE_SOURCEPAWN_JIT_RUNTIME_H_ diff --git a/sourcepawn/jit/md5/md5.cpp b/sourcepawn/jit/md5/md5.cpp new file mode 100644 index 00000000..8738c6df --- /dev/null +++ b/sourcepawn/jit/md5/md5.cpp @@ -0,0 +1,476 @@ +// MD5.CC - source code for the C++/object oriented translation and +// modification of MD5. + +// Translation and modification (c) 1995 by Mordechai T. Abzug + +// This translation/ modification is provided "as is," without express or +// implied warranty of any kind. + +// The translator/ modifier does not claim (1) that MD5 will do what you think +// it does; (2) that this translation/ modification is accurate; or (3) that +// this software is "merchantible." (Language for this disclaimer partially +// copied from the disclaimer below). + +/* based on: + + MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm + MDDRIVER.C - test driver for MD2, MD4 and MD5 + + + Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + + */ + +#include "md5.h" + +#include +#include + +// MD5 simple initialization method + +MD5::MD5(){ + + init(); + +} + + +// MD5 block update operation. Continues an MD5 message-digest +// operation, processing another message block, and updating the +// context. + +void MD5::update (uint1 *input, uint4 input_length) { + + uint4 input_index, buffer_index; + uint4 buffer_space; // how much space is left in buffer + + if (finalized){ // so we can't update! + /*cerr << "MD5::update: Can't update a finalized digest!" << endl;*/ + return; + } + + // Compute number of bytes mod 64 + buffer_index = (unsigned int)((count[0] >> 3) & 0x3F); + + // Update number of bits + if ( (count[0] += ((uint4) input_length << 3))<((uint4) input_length << 3) ) + count[1]++; + + count[1] += ((uint4)input_length >> 29); + + + buffer_space = 64 - buffer_index; // how much space is left in buffer + + // Transform as many times as possible. + if (input_length >= buffer_space) { // ie. we have enough to fill the buffer + // fill the rest of the buffer and transform + memcpy (buffer + buffer_index, input, buffer_space); + transform (buffer); + + // now, transform each 64-byte piece of the input, bypassing the buffer + for (input_index = buffer_space; input_index + 63 < input_length; + input_index += 64) + transform (input+input_index); + + buffer_index = 0; // so we can buffer remaining + } + else + input_index=0; // so we can buffer the whole input + + + // and here we do the buffering: + memcpy(buffer+buffer_index, input+input_index, input_length-input_index); +} + + + +// MD5 update for files. +// Like above, except that it works on files (and uses above as a primitive.) + +void MD5::update(FILE *file){ + + unsigned char buffer[1024]; + int len; + + while ((len=fread(buffer, 1, 1024, file))) + update(buffer, len); + + fclose (file); + +} + + +// MD5 finalization. Ends an MD5 message-digest operation, writing the +// the message digest and zeroizing the context. + + +void MD5::finalize (){ + + unsigned char bits[8]; + unsigned int index, padLen; + static uint1 PADDING[64]={ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + if (finalized){ + /* cerr << "MD5::finalize: Already finalized this digest!" << endl;*/ + return; + } + + // Save number of bits + encode (bits, count, 8); + + // Pad out to 56 mod 64. + index = (uint4) ((count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + update (PADDING, padLen); + + // Append length (before padding) + update (bits, 8); + + // Store state in digest + encode (digest, state, 16); + + // Zeroize sensitive information + memset (buffer, 0, sizeof(*buffer)); + + finalized=1; + +} + + + + +MD5::MD5(FILE *file){ + + init(); // must be called be all constructors + update(file); + finalize (); +} + +unsigned char *MD5::raw_digest(){ + + uint1 *s = new uint1[16]; + + if (!finalized){ +/* cerr << "MD5::raw_digest: Can't get digest if you haven't "<< + "finalized the digest!" <> 8) & 0xff); + output[j+2] = (uint1) ((input[i] >> 16) & 0xff); + output[j+3] = (uint1) ((input[i] >> 24) & 0xff); + } +} + + + + +// Decodes input (unsigned char) into output (UINT4). Assumes len is +// a multiple of 4. +void MD5::decode (uint4 *output, uint1 *input, uint4 len){ + + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((uint4)input[j]) | (((uint4)input[j+1]) << 8) | + (((uint4)input[j+2]) << 16) | (((uint4)input[j+3]) << 24); +} + + + + + +// Note: Replace "for loop" with standard memcpy if possible. +void MD5::memcpy (uint1 *output, uint1 *input, uint4 len){ + + unsigned int i; + + for (i = 0; i < len; i++) + output[i] = input[i]; +} + + + +// Note: Replace "for loop" with standard memset if possible. +void MD5::memset (uint1 *output, uint1 value, uint4 len){ + + unsigned int i; + + for (i = 0; i < len; i++) + output[i] = value; +} + + + +// ROTATE_LEFT rotates x left n bits. + +inline unsigned int MD5::rotate_left (uint4 x, uint4 n){ + return (x << n) | (x >> (32-n)) ; +} + + + + +// F, G, H and I are basic MD5 functions. + +inline unsigned int MD5::F (uint4 x, uint4 y, uint4 z){ + return (x & y) | (~x & z); +} + +inline unsigned int MD5::G (uint4 x, uint4 y, uint4 z){ + return (x & z) | (y & ~z); +} + +inline unsigned int MD5::H (uint4 x, uint4 y, uint4 z){ + return x ^ y ^ z; +} + +inline unsigned int MD5::I (uint4 x, uint4 y, uint4 z){ + return y ^ (x | ~z); +} + + + +// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +// Rotation is separate from addition to prevent recomputation. + + +inline void MD5::FF(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, + uint4 s, uint4 ac){ + a += F(b, c, d) + x + ac; + a = rotate_left (a, s) +b; +} + +inline void MD5::GG(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, + uint4 s, uint4 ac){ + a += G(b, c, d) + x + ac; + a = rotate_left (a, s) +b; +} + +inline void MD5::HH(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, + uint4 s, uint4 ac){ + a += H(b, c, d) + x + ac; + a = rotate_left (a, s) +b; +} + +inline void MD5::II(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, + uint4 s, uint4 ac){ + a += I(b, c, d) + x + ac; + a = rotate_left (a, s) +b; +} diff --git a/sourcepawn/jit/md5/md5.h b/sourcepawn/jit/md5/md5.h new file mode 100644 index 00000000..3f6979d9 --- /dev/null +++ b/sourcepawn/jit/md5/md5.h @@ -0,0 +1,106 @@ +// MD5.CC - source code for the C++/object oriented translation and +// modification of MD5. + +// Translation and modification (c) 1995 by Mordechai T. Abzug + +// This translation/ modification is provided "as is," without express or +// implied warranty of any kind. + +// The translator/ modifier does not claim (1) that MD5 will do what you think +// it does; (2) that this translation/ modification is accurate; or (3) that +// this software is "merchantible." (Language for this disclaimer partially +// copied from the disclaimer below). + +/* based on: + + MD5.H - header file for MD5C.C + MDDRIVER.C - test driver for MD2, MD4 and MD5 + + Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +*/ + +#include +//#include +//#include + +class MD5 { + +public: +// methods for controlled operation: + MD5 (); // simple initializer + void update (unsigned char *input, unsigned int input_length); + void update (FILE *file); + void finalize (); + +// constructors for special circumstances. All these constructors finalize +// the MD5 context. + MD5 (unsigned char *string); // digest string, finalize + MD5 (FILE *file); // digest file, close, finalize + +// methods to acquire finalized result + unsigned char *raw_digest (); // digest as a 16-byte binary array + unsigned char *raw_digest(unsigned char buffer[16]); + char * hex_digest (); // digest as a 33-byte ascii-hex string + char * hex_digest (char buffer[33]); //same as above, passing buffer + + + +private: + +// first, some types: + typedef unsigned int uint4; // assumes integer is 4 words long + typedef unsigned short int uint2; // assumes short integer is 2 words long + typedef unsigned char uint1; // assumes char is 1 word long + +// next, the private data: + uint4 state[4]; + uint4 count[2]; // number of *bits*, mod 2^64 + uint1 buffer[64]; // input buffer + uint1 digest[16]; + uint1 finalized; + +// last, the private methods, mostly static: + void init (); // called by all constructors + void transform (uint1 *buffer); // does the real update work. Note + // that length is implied to be 64. + + static void encode (uint1 *dest, uint4 *src, uint4 length); + static void decode (uint4 *dest, uint1 *src, uint4 length); + static void memcpy (uint1 *dest, uint1 *src, uint4 length); + static void memset (uint1 *start, uint1 val, uint4 length); + + static inline uint4 rotate_left (uint4 x, uint4 n); + static inline uint4 F (uint4 x, uint4 y, uint4 z); + static inline uint4 G (uint4 x, uint4 y, uint4 z); + static inline uint4 H (uint4 x, uint4 y, uint4 z); + static inline uint4 I (uint4 x, uint4 y, uint4 z); + static inline void FF (uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, + uint4 s, uint4 ac); + static inline void GG (uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, + uint4 s, uint4 ac); + static inline void HH (uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, + uint4 s, uint4 ac); + static inline void II (uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, + uint4 s, uint4 ac); + +};