Module sandboxing system

Sandboxing makes module scripts run inside an isolated lua environments,
making more easier and secure to script

Move and rework TextMessage using the new sandbox system
This commit is contained in:
Eduardo Bart
2012-07-19 06:12:17 -03:00
parent e2921c6407
commit f289db3a9e
17 changed files with 287 additions and 263 deletions

View File

@@ -22,6 +22,7 @@
#include "module.h"
#include "modulemanager.h"
#include "resourcemanager.h"
#include <framework/otml/otml.h>
#include <framework/luaengine/luainterface.h>
@@ -29,6 +30,8 @@
Module::Module(const std::string& name)
{
m_name = name;
g_lua.newEnvironment();
m_sandboxEnv = g_lua.ref();
}
bool Module::load()
@@ -36,24 +39,43 @@ bool Module::load()
if(m_loaded)
return true;
for(const std::string& depName : m_dependencies) {
ModulePtr dep = g_modules.getModule(depName);
if(!dep) {
g_logger.error(stdext::format("Unable to load module '%s' because dependency '%s' was not found", m_name, depName));
return false;
try {
for(const std::string& depName : m_dependencies) {
ModulePtr dep = g_modules.getModule(depName);
if(!dep)
stdext::throw_exception(stdext::format("dependency '%s' was not found", m_name, depName));
if(!dep->isLoaded() && !dep->load())
stdext::throw_exception(stdext::format("dependency '%s' has failed to load", m_name, depName));
}
if(!dep->isLoaded() && !dep->load()) {
g_logger.error(stdext::format("Unable to load module '%s' because dependency '%s' has failed to load", m_name, depName));
return false;
for(const std::string& script : m_scripts) {
g_lua.loadScript(script);
if(m_sandboxed) {
g_lua.getRef(m_sandboxEnv);
g_lua.setEnv();
}
g_lua.safeCall(0, 0);
}
const std::string& onLoadBuffer = std::get<0>(m_onLoadFunc);
const std::string& onLoadSource = std::get<1>(m_onLoadFunc);
if(!onLoadBuffer.empty()) {
g_lua.loadBuffer(onLoadBuffer, onLoadSource);
if(m_sandboxed) {
g_lua.getRef(m_sandboxEnv);
g_lua.setEnv();
}
g_lua.safeCall(0, 0);
}
g_logger.debug(stdext::format("Loaded module '%s'", m_name));
} catch(stdext::exception& e) {
g_logger.error(stdext::format("Unable to load module '%s': %s", m_name, e.what()));
return false;
}
if(m_loadCallback)
m_loadCallback();
m_loaded = true;
g_logger.debug(stdext::format("Loaded module '%s'", m_name));
g_modules.updateModuleLoadOrder(asModule());
for(const std::string& modName : m_loadLaterModules) {
@@ -70,8 +92,21 @@ bool Module::load()
void Module::unload()
{
if(m_loaded) {
if(m_unloadCallback)
m_unloadCallback();
try {
const std::string& onUnloadBuffer = std::get<0>(m_onUnloadFunc);
const std::string& onUnloadSource = std::get<1>(m_onUnloadFunc);
if(!onUnloadBuffer.empty()) {
g_lua.loadBuffer(onUnloadBuffer, onUnloadSource);
if(m_sandboxed) {
g_lua.getRef(m_sandboxEnv);
g_lua.setEnv();
}
g_lua.safeCall(0, 0);
}
} catch(stdext::exception& e) {
g_logger.error(stdext::format("Unable to unload module '%s': %s", m_name, e.what()));
}
m_loaded = false;
//g_logger.info(stdext::format("Unloaded module '%s'", m_name));
g_modules.updateModuleLoadOrder(asModule());
@@ -109,6 +144,7 @@ void Module::discover(const OTMLNodePtr& moduleNode)
m_version = moduleNode->valueAt("version", none);
m_autoLoad = moduleNode->valueAt<bool>("autoload", false);
m_reloadable = moduleNode->valueAt<bool>("reloadable", true);
m_sandboxed = moduleNode->valueAt<bool>("sandboxed", false);
m_autoLoadPriority = moduleNode->valueAt<int>("autoload-priority", 9999);
if(OTMLNodePtr node = moduleNode->get("dependencies")) {
@@ -116,22 +152,19 @@ void Module::discover(const OTMLNodePtr& moduleNode)
m_dependencies.push_back(tmp->value());
}
// set onLoad callback
if(OTMLNodePtr node = moduleNode->get("@onLoad")) {
g_lua.loadFunction(node->value(), "@" + node->source() + "[" + node->tag() + "]");
g_lua.useValue();
m_loadCallback = g_lua.polymorphicPop<std::function<void()>>();
}
// set onUnload callback
if(OTMLNodePtr node = moduleNode->get("@onUnload")) {
g_lua.loadFunction(node->value(), "@" + node->source() + "[" + node->tag() + "]");
g_lua.useValue();
m_unloadCallback = g_lua.polymorphicPop<std::function<void()>>();
if(OTMLNodePtr node = moduleNode->get("scripts")) {
for(const OTMLNodePtr& tmp : node->children())
m_scripts.push_back(stdext::resolve_path(tmp->value(), node->source()));
}
if(OTMLNodePtr node = moduleNode->get("load-later")) {
for(const OTMLNodePtr& tmp : node->children())
m_loadLaterModules.push_back(tmp->value());
}
if(OTMLNodePtr node = moduleNode->get("@onLoad"))
m_onLoadFunc = std::make_tuple(node->value(), "@" + node->source() + "[" + node->tag() + "]");
if(OTMLNodePtr node = moduleNode->get("@onUnload"))
m_onUnloadFunc = std::make_tuple(node->value(), "@" + node->source() + "[" + node->tag() + "]");
}

View File

@@ -64,7 +64,11 @@ private:
Boolean<false> m_loaded;
Boolean<false> m_autoLoad;
Boolean<false> m_reloadable;
Boolean<false> m_sandboxed;
int m_autoLoadPriority;
int m_sandboxEnv;
std::tuple<std::string, std::string> m_onLoadFunc;
std::tuple<std::string, std::string> m_onUnloadFunc;
std::string m_name;
std::string m_description;
std::string m_author;
@@ -73,6 +77,7 @@ private:
std::function<void()> m_loadCallback;
std::function<void()> m_unloadCallback;
std::list<std::string> m_dependencies;
std::list<std::string> m_scripts;
std::list<std::string> m_loadLaterModules;
};

View File

@@ -306,13 +306,13 @@ bool LuaInterface::safeRunScript(const std::string& fileName)
void LuaInterface::runScript(const std::string& fileName)
{
loadScript(fileName);
safeCall();
safeCall(0, 0);
}
void LuaInterface::runBuffer(const std::string& buffer, const std::string& source)
{
loadBuffer(buffer, source);
safeCall();
safeCall(0, 0);
}
void LuaInterface::loadScript(const std::string& fileName)
@@ -425,7 +425,7 @@ std::string LuaInterface::getCurrentSourcePath(int level)
return path;
}
int LuaInterface::safeCall(int numArgs)
int LuaInterface::safeCall(int numArgs, int numRets)
{
assert(hasIndex(-numArgs-1));
@@ -446,22 +446,33 @@ int LuaInterface::safeCall(int numArgs)
if(ret != 0)
throw LuaException(popString());
int rets = (stackSize() + numArgs + 1) - previousStackSize;
while(numRets != -1 && rets != numRets) {
if(rets < numRets) {
pushNil();
rets++;
} else {
pop();
rets--;
}
}
// returns the number of results
return (stackSize() + numArgs + 1) - previousStackSize;
return rets;
}
int LuaInterface::signalCall(int numArgs, int requestedResults)
int LuaInterface::signalCall(int numArgs, int numRets)
{
int numRets = 0;
int rets = 0;
int funcIndex = -numArgs-1;
try {
// must be a function
if(isFunction(funcIndex)) {
numRets = safeCall(numArgs);
rets = safeCall(numArgs);
if(requestedResults != -1) {
if(numRets != requestedResults)
if(numRets != -1) {
if(rets != numRets)
throw LuaException("function call didn't return the expected number of results", 0);
}
}
@@ -491,8 +502,8 @@ int LuaInterface::signalCall(int numArgs, int requestedResults)
}
pop(numArgs + 1); // pops the table of function and arguments
if(requestedResults == 1) {
numRets = 1;
if(numRets == 1) {
rets = 1;
pushBoolean(done);
}
}
@@ -509,13 +520,13 @@ int LuaInterface::signalCall(int numArgs, int requestedResults)
}
// pushes nil values if needed
while(requestedResults != -1 && numRets < requestedResults) {
while(numRets != -1 && rets < numRets) {
pushNil();
numRets++;
rets++;
}
// returns the number of results on the stack
return numRets;
return rets;
}
void LuaInterface::newEnvironment()

View File

@@ -174,13 +174,13 @@ public:
/// results are pushed onto the stack.
/// @exception LuaException is thrown on any lua error
/// @return number of results
int safeCall(int numArgs = 0);
int safeCall(int numArgs = 0, int numRets = -1);
/// Same as safeCall but catches exceptions and can also calls a table of functions,
/// if any error occurs it will be reported to stdout and returns 0 results
/// @param requestedResults is the number of results requested to pushes onto the stack,
/// if supplied, the call will always pushes that number of results, even if it fails
int signalCall(int numArgs = 0, int requestedResults = -1);
int signalCall(int numArgs = 0, int numRets = -1);
/// @brief Creates a new environment table
/// The new environment table is redirected to the global environment (aka _G),

View File

@@ -60,7 +60,7 @@ protected:
void processWalkCancel(Otc::Direction direction);
// message related
void processTextMessage(const std::string& type, const std::string& message);
void processTextMessage(const std::string& type, const std::string& message); // deprecated
void processCreatureSpeak(const std::string& name, int level, Otc::SpeakType type, const std::string& message, int channelId, const Position& creaturePos);
// container related

View File

@@ -556,7 +556,6 @@ void Map::clean()
m_towns.clear();
m_houses.clear();
m_creatures.clear();
m_monsters.clear();
m_tilesRect = Rect(65534, 65534, 0, 0);
}

View File

@@ -329,77 +329,6 @@ namespace Proto {
#endif
};
enum MessageTypes {
#if PROTOCOL>=910
// 1-3
MessageConsoleBlue = 4, // old
// 5-11
MessageConsoleRed = 12, // old
// 13-15
MessageStatusDefault = 16, // old
MessageWarning, // old
MessageEventAdvance, // old
MessageStatusSmall, // old
MessageInfoDescription, // old
MessageDamageDealt, // new
MessageDamageReceived, // new
MessageHealed, // new
MessageExperience, // new
MessageDamageOthers, // new
MessageHealedOthers, // new
MessageExperienceOthers, // new
MessageEventDefault, // old
MessageLoot, // new
MessageTradeNpc, // unused
MessageChannelGuild, // new
MessagePartyManagment, // unused
MessageParty, // unused
MessageEventOrange, // old
MessageConsoleOrange, // old
MessageReport, // unused
MessageHotkeyUse, // unused
MessageTutorialHint, // unused
// unsupported
MessageConsoleOrange2 = 255
#elif PROTOCOL>=861
MessageConsoleOrange = 13,
MessageConsoleOrange2,
MessageWarning,
MessageEventAdvance,
MessageEventDefault,
MessageStatusDefault,
MessageInfoDescription,
MessageStatusSmall,
MessageConsoleBlue,
MessageConsoleRed
#elif PROTOCOL>=854
MessageConsoleRed = 18,
MessageConsoleOrange,
MessageConsoleOrange2,
MessageWarning,
MessageEventAdvance,
MessageEventDefault,
MessageStatusDefault,
MessageInfoDescription,
MessageStatusSmall,
MessageConsoleBlue
#elif PROTOCOL>=810
MessageWarning = 18,
MessageEventAdvance,
MessageEventDefault,
MessageStatusDefault,
MessageInfoDescription,
MessageStatusSmall,
MessageConsoleBlue,
MessageConsoleRed,
// unsupported
MessageConsoleOrange = 255,
MessageConsoleOrange2,
#endif
};
enum CreatureType {
CreatureTypePlayer = 0,
CreatureTypeMonster,
@@ -459,24 +388,6 @@ namespace Proto {
return Proto::ServerSpeakSay;
}
}
inline std::string translateTextMessageType(int type) {
switch(type) {
case Proto::MessageConsoleOrange: return "consoleOrange";
case Proto::MessageConsoleOrange2: return "consoleOrange";
case Proto::MessageWarning: return "warning";
case Proto::MessageEventAdvance: return "eventAdvance";
case Proto::MessageEventDefault: return "eventDefault";
case Proto::MessageStatusDefault: return "statusDefault";
case Proto::MessageInfoDescription: return "infoDescription";
case Proto::MessageStatusSmall: return "statusSmall";
case Proto::MessageConsoleBlue: return "consoleBlue";
case Proto::MessageConsoleRed: return "consoleRed";
default:
g_logger.error(stdext::format("unknown protocol text message type %d", type));
return "unknown";
}
}
}
#endif

View File

@@ -1067,12 +1067,9 @@ void ProtocolGame::parseRuleViolationLock(const InputMessagePtr& msg)
void ProtocolGame::parseTextMessage(const InputMessagePtr& msg)
{
int type = msg->getU8();
std::string typeDesc = Proto::translateTextMessageType(type);
std::string message = msg->getString();
g_game.processTextMessage(typeDesc, message);
msg->getU8(); // type
msg->getString(); // message
// this is now handled by game_textmessage module
}
void ProtocolGame::parseCancelWalk(const InputMessagePtr& msg)