Implement sandbox mode for the config parser

fixes #9068
This commit is contained in:
Gunnar Beutner 2015-04-16 08:47:26 +02:00
parent 9a3326fe66
commit d93bcedaad
15 changed files with 113 additions and 62 deletions

View File

@ -129,15 +129,15 @@ Object::Ptr Array::GetPrototype(void)
if (!prototype) { if (!prototype) {
prototype = new Dictionary(); prototype = new Dictionary();
prototype->Set("len", new Function(WrapFunction(ArrayLen))); prototype->Set("len", new Function(WrapFunction(ArrayLen), true));
prototype->Set("set", new Function(WrapFunction(ArraySet))); prototype->Set("set", new Function(WrapFunction(ArraySet)));
prototype->Set("add", new Function(WrapFunction(ArrayAdd))); prototype->Set("add", new Function(WrapFunction(ArrayAdd)));
prototype->Set("remove", new Function(WrapFunction(ArrayRemove))); prototype->Set("remove", new Function(WrapFunction(ArrayRemove)));
prototype->Set("contains", new Function(WrapFunction(ArrayContains))); prototype->Set("contains", new Function(WrapFunction(ArrayContains), true));
prototype->Set("clear", new Function(WrapFunction(ArrayClear))); prototype->Set("clear", new Function(WrapFunction(ArrayClear)));
prototype->Set("sort", new Function(WrapFunction(ArraySort))); prototype->Set("sort", new Function(WrapFunction(ArraySort), true));
prototype->Set("clone", new Function(WrapFunction(ArrayClone))); prototype->Set("clone", new Function(WrapFunction(ArrayClone), true));
prototype->Set("join", new Function(WrapFunction(ArrayJoin))); prototype->Set("join", new Function(WrapFunction(ArrayJoin), true));
} }
return prototype; return prototype;

View File

@ -38,7 +38,7 @@ Object::Ptr Boolean::GetPrototype(void)
if (!prototype) { if (!prototype) {
prototype = new Dictionary(); prototype = new Dictionary();
prototype->Set("to_string", new Function(WrapFunction(BooleanToString))); prototype->Set("to_string", new Function(WrapFunction(BooleanToString), true));
} }
return prototype; return prototype;

View File

@ -65,11 +65,11 @@ Object::Ptr Dictionary::GetPrototype(void)
if (!prototype) { if (!prototype) {
prototype = new Dictionary(); prototype = new Dictionary();
prototype->Set("len", new Function(WrapFunction(DictionaryLen))); prototype->Set("len", new Function(WrapFunction(DictionaryLen), true));
prototype->Set("set", new Function(WrapFunction(DictionarySet))); prototype->Set("set", new Function(WrapFunction(DictionarySet)));
prototype->Set("remove", new Function(WrapFunction(DictionaryRemove))); prototype->Set("remove", new Function(WrapFunction(DictionaryRemove)));
prototype->Set("contains", new Function(WrapFunction(DictionaryContains))); prototype->Set("contains", new Function(WrapFunction(DictionaryContains), true));
prototype->Set("clone", new Function(WrapFunction(DictionaryClone))); prototype->Set("clone", new Function(WrapFunction(DictionaryClone), true));
} }
return prototype; return prototype;

View File

@ -25,8 +25,8 @@ using namespace icinga;
REGISTER_PRIMITIVE_TYPE_NOINST(Function, Function::GetPrototype()); REGISTER_PRIMITIVE_TYPE_NOINST(Function, Function::GetPrototype());
Function::Function(const Callback& function) Function::Function(const Callback& function, bool side_effect_free)
: m_Callback(function) : m_Callback(function), m_SideEffectFree(side_effect_free)
{ } { }
Value Function::Invoke(const std::vector<Value>& arguments) Value Function::Invoke(const std::vector<Value>& arguments)
@ -34,3 +34,8 @@ Value Function::Invoke(const std::vector<Value>& arguments)
return m_Callback(arguments); return m_Callback(arguments);
} }
bool Function::IsSideEffectFree(void) const
{
return m_SideEffectFree;
}

View File

@ -42,14 +42,16 @@ public:
typedef boost::function<Value (const std::vector<Value>& arguments)> Callback; typedef boost::function<Value (const std::vector<Value>& arguments)> Callback;
Function(const Callback& function); Function(const Callback& function, bool side_effect_free = false);
Value Invoke(const std::vector<Value>& arguments = std::vector<Value>()); Value Invoke(const std::vector<Value>& arguments = std::vector<Value>());
bool IsSideEffectFree(void) const;
static Object::Ptr GetPrototype(void); static Object::Ptr GetPrototype(void);
private: private:
Callback m_Callback; Callback m_Callback;
bool m_SideEffectFree;
}; };
#define REGISTER_SCRIPTFUNCTION(name, callback) \ #define REGISTER_SCRIPTFUNCTION(name, callback) \
@ -61,6 +63,15 @@ private:
INITIALIZE_ONCE(RegisterFunction); \ INITIALIZE_ONCE(RegisterFunction); \
} } } } } }
#define REGISTER_SAFE_SCRIPTFUNCTION(name, callback) \
namespace { namespace UNIQUE_NAME(sf) { namespace sf ## name { \
void RegisterFunction(void) { \
Function::Ptr sf = new icinga::Function(WrapFunction(callback), true); \
ScriptGlobal::Set(#name, sf); \
} \
INITIALIZE_ONCE(RegisterFunction); \
} } }
} }
#endif /* SCRIPTFUNCTION_H */ #endif /* SCRIPTFUNCTION_H */

View File

@ -36,8 +36,8 @@ static void InitializeJsonObj(void)
Dictionary::Ptr jsonObj = new Dictionary(); Dictionary::Ptr jsonObj = new Dictionary();
/* Methods */ /* Methods */
jsonObj->Set("encode", new Function(WrapFunction(JsonEncodeShim))); jsonObj->Set("encode", new Function(WrapFunction(JsonEncodeShim), true));
jsonObj->Set("decode", new Function(WrapFunction(JsonDecode))); jsonObj->Set("decode", new Function(WrapFunction(JsonDecode), true));
ScriptGlobal::Set("Json", jsonObj); ScriptGlobal::Set("Json", jsonObj);
} }

View File

@ -174,27 +174,27 @@ static void InitializeMathObj(void)
mathObj->Set("SQRT2", 1.41421356237309504880); mathObj->Set("SQRT2", 1.41421356237309504880);
/* Methods */ /* Methods */
mathObj->Set("abs", new Function(WrapFunction(MathAbs))); mathObj->Set("abs", new Function(WrapFunction(MathAbs), true));
mathObj->Set("acos", new Function(WrapFunction(MathAcos))); mathObj->Set("acos", new Function(WrapFunction(MathAcos), true));
mathObj->Set("asin", new Function(WrapFunction(MathAsin))); mathObj->Set("asin", new Function(WrapFunction(MathAsin), true));
mathObj->Set("atan", new Function(WrapFunction(MathAtan))); mathObj->Set("atan", new Function(WrapFunction(MathAtan), true));
mathObj->Set("atan2", new Function(WrapFunction(MathAtan2))); mathObj->Set("atan2", new Function(WrapFunction(MathAtan2), true));
mathObj->Set("ceil", new Function(WrapFunction(MathCeil))); mathObj->Set("ceil", new Function(WrapFunction(MathCeil), true));
mathObj->Set("cos", new Function(WrapFunction(MathCos))); mathObj->Set("cos", new Function(WrapFunction(MathCos), true));
mathObj->Set("exp", new Function(WrapFunction(MathExp))); mathObj->Set("exp", new Function(WrapFunction(MathExp), true));
mathObj->Set("floor", new Function(WrapFunction(MathFloor))); mathObj->Set("floor", new Function(WrapFunction(MathFloor), true));
mathObj->Set("log", new Function(WrapFunction(MathLog))); mathObj->Set("log", new Function(WrapFunction(MathLog), true));
mathObj->Set("max", new Function(WrapFunction(MathMax))); mathObj->Set("max", new Function(WrapFunction(MathMax), true));
mathObj->Set("min", new Function(WrapFunction(MathMin))); mathObj->Set("min", new Function(WrapFunction(MathMin), true));
mathObj->Set("pow", new Function(WrapFunction(MathPow))); mathObj->Set("pow", new Function(WrapFunction(MathPow), true));
mathObj->Set("random", new Function(WrapFunction(MathRandom))); mathObj->Set("random", new Function(WrapFunction(MathRandom), true));
mathObj->Set("round", new Function(WrapFunction(MathRound))); mathObj->Set("round", new Function(WrapFunction(MathRound), true));
mathObj->Set("sin", new Function(WrapFunction(MathSin))); mathObj->Set("sin", new Function(WrapFunction(MathSin), true));
mathObj->Set("sqrt", new Function(WrapFunction(MathSqrt))); mathObj->Set("sqrt", new Function(WrapFunction(MathSqrt), true));
mathObj->Set("tan", new Function(WrapFunction(MathTan))); mathObj->Set("tan", new Function(WrapFunction(MathTan), true));
mathObj->Set("isnan", new Function(WrapFunction(MathIsnan))); mathObj->Set("isnan", new Function(WrapFunction(MathIsnan), true));
mathObj->Set("isinf", new Function(WrapFunction(MathIsinf))); mathObj->Set("isinf", new Function(WrapFunction(MathIsinf), true));
mathObj->Set("sign", new Function(WrapFunction(MathSign))); mathObj->Set("sign", new Function(WrapFunction(MathSign), true));
ScriptGlobal::Set("Math", mathObj); ScriptGlobal::Set("Math", mathObj);
} }

View File

@ -38,7 +38,7 @@ Object::Ptr Number::GetPrototype(void)
if (!prototype) { if (!prototype) {
prototype = new Dictionary(); prototype = new Dictionary();
prototype->Set("to_string", new Function(WrapFunction(NumberToString))); prototype->Set("to_string", new Function(WrapFunction(NumberToString), true));
} }
return prototype; return prototype;

View File

@ -38,7 +38,7 @@ Object::Ptr Object::GetPrototype(void)
if (!prototype) { if (!prototype) {
prototype = new Dictionary(); prototype = new Dictionary();
prototype->Set("to_string", new Function(WrapFunction(ObjectToString))); prototype->Set("to_string", new Function(WrapFunction(ObjectToString), true));
} }
return prototype; return prototype;

View File

@ -25,13 +25,13 @@ using namespace icinga;
boost::thread_specific_ptr<std::stack<ScriptFrame *> > ScriptFrame::m_ScriptFrames; boost::thread_specific_ptr<std::stack<ScriptFrame *> > ScriptFrame::m_ScriptFrames;
ScriptFrame::ScriptFrame(void) ScriptFrame::ScriptFrame(void)
: Locals(new Dictionary()), Self(ScriptGlobal::GetGlobals()) : Locals(new Dictionary()), Self(ScriptGlobal::GetGlobals()), Sandboxed(false)
{ {
PushFrame(this); PushFrame(this);
} }
ScriptFrame::ScriptFrame(const Value& self) ScriptFrame::ScriptFrame(const Value& self)
: Locals(new Dictionary()), Self(self) : Locals(new Dictionary()), Self(self), Sandboxed(false)
{ {
PushFrame(this); PushFrame(this);
} }

View File

@ -32,6 +32,7 @@ struct I2_BASE_API ScriptFrame
{ {
Dictionary::Ptr Locals; Dictionary::Ptr Locals;
Value Self; Value Self;
bool Sandboxed;
ScriptFrame(void); ScriptFrame(void);
ScriptFrame(const Value& self); ScriptFrame(const Value& self);

View File

@ -33,24 +33,24 @@
using namespace icinga; using namespace icinga;
REGISTER_SCRIPTFUNCTION(regex, &ScriptUtils::Regex); REGISTER_SAFE_SCRIPTFUNCTION(regex, &ScriptUtils::Regex);
REGISTER_SCRIPTFUNCTION(match, &Utility::Match); REGISTER_SAFE_SCRIPTFUNCTION(match, &Utility::Match);
REGISTER_SCRIPTFUNCTION(len, &ScriptUtils::Len); REGISTER_SAFE_SCRIPTFUNCTION(len, &ScriptUtils::Len);
REGISTER_SCRIPTFUNCTION(union, &ScriptUtils::Union); REGISTER_SAFE_SCRIPTFUNCTION(union, &ScriptUtils::Union);
REGISTER_SCRIPTFUNCTION(intersection, &ScriptUtils::Intersection); REGISTER_SAFE_SCRIPTFUNCTION(intersection, &ScriptUtils::Intersection);
REGISTER_SCRIPTFUNCTION(log, &ScriptUtils::Log); REGISTER_SCRIPTFUNCTION(log, &ScriptUtils::Log);
REGISTER_SCRIPTFUNCTION(range, &ScriptUtils::Range); REGISTER_SCRIPTFUNCTION(range, &ScriptUtils::Range);
REGISTER_SCRIPTFUNCTION(exit, &Application::Exit); REGISTER_SCRIPTFUNCTION(exit, &Application::Exit);
REGISTER_SCRIPTFUNCTION(typeof, &ScriptUtils::TypeOf); REGISTER_SAFE_SCRIPTFUNCTION(typeof, &ScriptUtils::TypeOf);
REGISTER_SCRIPTFUNCTION(keys, &ScriptUtils::Keys); REGISTER_SAFE_SCRIPTFUNCTION(keys, &ScriptUtils::Keys);
REGISTER_SCRIPTFUNCTION(random, &Utility::Random); REGISTER_SAFE_SCRIPTFUNCTION(random, &Utility::Random);
REGISTER_SCRIPTFUNCTION(get_object, &ScriptUtils::GetObject); REGISTER_SAFE_SCRIPTFUNCTION(get_object, &ScriptUtils::GetObject);
REGISTER_SCRIPTFUNCTION(get_objects, &ScriptUtils::GetObjects); REGISTER_SAFE_SCRIPTFUNCTION(get_objects, &ScriptUtils::GetObjects);
REGISTER_SCRIPTFUNCTION(assert, &ScriptUtils::Assert); REGISTER_SCRIPTFUNCTION(assert, &ScriptUtils::Assert);
REGISTER_SCRIPTFUNCTION(string, &ScriptUtils::CastString); REGISTER_SAFE_SCRIPTFUNCTION(string, &ScriptUtils::CastString);
REGISTER_SCRIPTFUNCTION(number, &ScriptUtils::CastNumber); REGISTER_SAFE_SCRIPTFUNCTION(number, &ScriptUtils::CastNumber);
REGISTER_SCRIPTFUNCTION(bool, &ScriptUtils::CastBool); REGISTER_SAFE_SCRIPTFUNCTION(bool, &ScriptUtils::CastBool);
REGISTER_SCRIPTFUNCTION(get_time, &Utility::GetTime); REGISTER_SAFE_SCRIPTFUNCTION(get_time, &Utility::GetTime);
String ScriptUtils::CastString(const Value& value) String ScriptUtils::CastString(const Value& value)
{ {

View File

@ -133,15 +133,15 @@ Object::Ptr String::GetPrototype(void)
if (!prototype) { if (!prototype) {
prototype = new Dictionary(); prototype = new Dictionary();
prototype->Set("len", new Function(WrapFunction(StringLen))); prototype->Set("len", new Function(WrapFunction(StringLen), true));
prototype->Set("to_string", new Function(WrapFunction(StringToString))); prototype->Set("to_string", new Function(WrapFunction(StringToString), true));
prototype->Set("substr", new Function(WrapFunction(StringSubstr))); prototype->Set("substr", new Function(WrapFunction(StringSubstr), true));
prototype->Set("upper", new Function(WrapFunction(StringUpper))); prototype->Set("upper", new Function(WrapFunction(StringUpper), true));
prototype->Set("lower", new Function(WrapFunction(StringLower))); prototype->Set("lower", new Function(WrapFunction(StringLower), true));
prototype->Set("split", new Function(WrapFunction(StringSplit))); prototype->Set("split", new Function(WrapFunction(StringSplit), true));
prototype->Set("find", new Function(WrapFunction(StringFind))); prototype->Set("find", new Function(WrapFunction(StringFind), true));
prototype->Set("contains", new Function(WrapFunction(StringContains))); prototype->Set("contains", new Function(WrapFunction(StringContains), true));
prototype->Set("replace", new Function(WrapFunction(StringReplace))); prototype->Set("replace", new Function(WrapFunction(StringReplace), true));
} }
return prototype; return prototype;

View File

@ -59,6 +59,7 @@ void ConsoleCommand::InitParameters(boost::program_options::options_description&
{ {
visibleDesc.add_options() visibleDesc.add_options()
("connect,c", po::value<std::string>(), "connect to an Icinga 2 instance") ("connect,c", po::value<std::string>(), "connect to an Icinga 2 instance")
("sandbox", "enable sandbox mode")
; ;
} }
@ -194,6 +195,15 @@ int ConsoleCommand::Run(const po::variables_map& vm, const std::vector<std::stri
session = Utility::NewUniqueID(); session = Utility::NewUniqueID();
} }
if (vm.count("sandbox")) {
if (vm.count("connect")) {
Log(LogCritical, "ConsoleCommand", "Sandbox mode cannot be used together with --connect.");
return EXIT_FAILURE;
}
l_ScriptFrame.Sandboxed = true;
}
std::cout << "Icinga (version: " << Application::GetVersion() << ")\n"; std::cout << "Icinga (version: " << Application::GetVersion() << ")\n";
while (std::cin.good()) { while (std::cin.good()) {

View File

@ -414,6 +414,9 @@ ExpressionResult FunctionCallExpression::DoEvaluate(ScriptFrame& frame, DebugHin
Function::Ptr func = vfunc; Function::Ptr func = vfunc;
if (!func->IsSideEffectFree() && frame.Sandboxed)
BOOST_THROW_EXCEPTION(ScriptError("Function is not marked as safe for sandbox mode.", m_DebugInfo));
std::vector<Value> arguments; std::vector<Value> arguments;
BOOST_FOREACH(Expression *arg, m_Args) { BOOST_FOREACH(Expression *arg, m_Args) {
ExpressionResult argres = arg->Evaluate(frame); ExpressionResult argres = arg->Evaluate(frame);
@ -483,6 +486,9 @@ ExpressionResult GetScopeExpression::DoEvaluate(ScriptFrame& frame, DebugHint *d
ExpressionResult SetExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const ExpressionResult SetExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{ {
if (frame.Sandboxed)
BOOST_THROW_EXCEPTION(ScriptError("Assignments are not allowed in sandbox mode.", m_DebugInfo));
DebugHint *psdhint = dhint; DebugHint *psdhint = dhint;
Value parent; Value parent;
@ -565,6 +571,9 @@ ExpressionResult ConditionalExpression::DoEvaluate(ScriptFrame& frame, DebugHint
ExpressionResult WhileExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const ExpressionResult WhileExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{ {
if (frame.Sandboxed)
BOOST_THROW_EXCEPTION(ScriptError("While loops are not allowed in sandbox mode.", m_DebugInfo));
for (;;) { for (;;) {
ExpressionResult condition = m_Condition->Evaluate(frame, dhint); ExpressionResult condition = m_Condition->Evaluate(frame, dhint);
CHECK_RESULT(condition); CHECK_RESULT(condition);
@ -618,6 +627,9 @@ bool IndexerExpression::GetReference(ScriptFrame& frame, bool init_dict, Value *
if (dhint) if (dhint)
psdhint = *dhint; psdhint = *dhint;
if (frame.Sandboxed)
init_dict = false;
if (m_Operand1->GetReference(frame, init_dict, &vparent, &vindex, &psdhint)) { if (m_Operand1->GetReference(frame, init_dict, &vparent, &vindex, &psdhint)) {
if (init_dict && VMOps::GetField(vparent, vindex, m_Operand1->GetDebugInfo()).IsEmpty()) if (init_dict && VMOps::GetField(vparent, vindex, m_Operand1->GetDebugInfo()).IsEmpty())
VMOps::SetField(vparent, vindex, new Dictionary(), m_Operand1->GetDebugInfo()); VMOps::SetField(vparent, vindex, new Dictionary(), m_Operand1->GetDebugInfo());
@ -687,6 +699,9 @@ void icinga::BindToScope(Expression *& expr, ScopeSpecifier scopeSpec)
ExpressionResult ImportExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const ExpressionResult ImportExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{ {
if (frame.Sandboxed)
BOOST_THROW_EXCEPTION(ScriptError("Imports are not allowed in sandbox mode.", m_DebugInfo));
String type = VMOps::GetField(frame.Self, "type", m_DebugInfo); String type = VMOps::GetField(frame.Self, "type", m_DebugInfo);
ExpressionResult nameres = m_Name->Evaluate(frame); ExpressionResult nameres = m_Name->Evaluate(frame);
CHECK_RESULT(nameres); CHECK_RESULT(nameres);
@ -713,6 +728,9 @@ ExpressionResult FunctionExpression::DoEvaluate(ScriptFrame& frame, DebugHint *d
ExpressionResult ApplyExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const ExpressionResult ApplyExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{ {
if (frame.Sandboxed)
BOOST_THROW_EXCEPTION(ScriptError("Apply rules are not allowed in sandbox mode.", m_DebugInfo));
ExpressionResult nameres = m_Name->Evaluate(frame); ExpressionResult nameres = m_Name->Evaluate(frame);
CHECK_RESULT(nameres); CHECK_RESULT(nameres);
@ -722,6 +740,9 @@ ExpressionResult ApplyExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhin
ExpressionResult ObjectExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const ExpressionResult ObjectExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{ {
if (frame.Sandboxed)
BOOST_THROW_EXCEPTION(ScriptError("Object definitions are not allowed in sandbox mode.", m_DebugInfo));
String name; String name;
if (m_Name) { if (m_Name) {
@ -737,6 +758,9 @@ ExpressionResult ObjectExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhi
ExpressionResult ForExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const ExpressionResult ForExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{ {
if (frame.Sandboxed)
BOOST_THROW_EXCEPTION(ScriptError("For loops are not allowed in sandbox mode.", m_DebugInfo));
ExpressionResult valueres = m_Value->Evaluate(frame, dhint); ExpressionResult valueres = m_Value->Evaluate(frame, dhint);
CHECK_RESULT(valueres); CHECK_RESULT(valueres);