mirror of
https://github.com/Icinga/icinga2.git
synced 2026-07-15 19:52:52 -04:00
Recently, with fb42312e6c, we've added a limit to
how deeply nested data structures can be created from API requests to limit the
resulting recursion depth when processing them to prevent stack overflows.
However, this resulted in a slight inconsistency (not relevant for security)
where you may create custom variables with different nesting depth limits
depending on the exact way to create them. This change now introduces an
additional validation for custom variables consistently limiting there overall
depth. This was not done yet as part of the security fixes as this also affects
existing configuration in a way that might cause existing configuration now
fail the validation. The limit of 16 should be large enough so that should
affect none or very few users.
267 lines
7.1 KiB
C++
267 lines
7.1 KiB
C++
// SPDX-FileCopyrightText: 2012 Icinga GmbH <https://icinga.com>
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "base/configwriter.hpp"
|
|
#include "base/exception.hpp"
|
|
#include <boost/regex.hpp>
|
|
#include <boost/algorithm/string/replace.hpp>
|
|
#include <set>
|
|
#include <iterator>
|
|
|
|
using namespace icinga;
|
|
|
|
void ConfigWriter::EmitBoolean(std::ostream& fp, bool val)
|
|
{
|
|
fp << (val ? "true" : "false");
|
|
}
|
|
|
|
void ConfigWriter::EmitNumber(std::ostream& fp, double val)
|
|
{
|
|
fp << std::fixed << val;
|
|
}
|
|
|
|
void ConfigWriter::EmitString(std::ostream& fp, const String& val)
|
|
{
|
|
fp << "\"" << EscapeIcingaString(val) << "\"";
|
|
}
|
|
|
|
void ConfigWriter::EmitEmpty(std::ostream& fp)
|
|
{
|
|
fp << "null";
|
|
}
|
|
|
|
void ConfigWriter::EmitArray(std::ostream& fp, int indentLevel, const Array::Ptr& val)
|
|
{
|
|
fp << "[ ";
|
|
EmitArrayItems(fp, indentLevel, val);
|
|
if (val->GetLength() > 0)
|
|
fp << " ";
|
|
fp << "]";
|
|
}
|
|
|
|
void ConfigWriter::EmitArrayItems(std::ostream& fp, int indentLevel, const Array::Ptr& val)
|
|
{
|
|
bool first = true;
|
|
|
|
ObjectLock olock(val);
|
|
for (const Value& item : val) {
|
|
if (first)
|
|
first = false;
|
|
else
|
|
fp << ", ";
|
|
|
|
EmitValue(fp, indentLevel, item);
|
|
}
|
|
}
|
|
|
|
void ConfigWriter::EmitScope(std::ostream& fp, int indentLevel, const Dictionary::Ptr& val,
|
|
const Array::Ptr& imports, bool splitDot)
|
|
{
|
|
fp << "{";
|
|
|
|
if (imports && imports->GetLength() > 0) {
|
|
ObjectLock xlock(imports);
|
|
for (const Value& import : imports) {
|
|
fp << "\n";
|
|
EmitIndent(fp, indentLevel);
|
|
fp << "import ";
|
|
EmitString(fp, import.Get<String>());
|
|
}
|
|
|
|
fp << "\n";
|
|
}
|
|
|
|
if (val) {
|
|
ObjectLock olock(val);
|
|
for (const Dictionary::Pair& kv : val) {
|
|
fp << "\n";
|
|
EmitIndent(fp, indentLevel);
|
|
|
|
if (splitDot) {
|
|
std::vector<String> tokens = kv.first.Split(".");
|
|
|
|
// This is reachable from CreateObjectHandler. This check prevents API clients from creating deeply
|
|
// nested data structures that could overflow the stack later on. It is done in addition to the check in
|
|
// CustomVarObject::ValidateDepthLimit() which is indirectly called by the validation function below.
|
|
// The latter does not stop offending data structures from being created (whereas this one does), but
|
|
// only ensures a consistent limit overall with different ways how custom variables can be created.
|
|
if (tokens.size() > ConfigObject::VarDepthLimit) {
|
|
BOOST_THROW_EXCEPTION(std::invalid_argument("Attribute '" + kv.first +
|
|
"' exceeds maximum nesting level of " + std::to_string(ConfigObject::VarDepthLimit) + "."));
|
|
}
|
|
|
|
EmitIdentifier(fp, tokens[0], true);
|
|
|
|
for (std::vector<String>::size_type i = 1; i < tokens.size(); i++) {
|
|
fp << "[";
|
|
EmitString(fp, tokens[i]);
|
|
fp << "]";
|
|
}
|
|
} else
|
|
EmitIdentifier(fp, kv.first, true);
|
|
|
|
fp << " = ";
|
|
EmitValue(fp, indentLevel + 1, kv.second);
|
|
}
|
|
}
|
|
|
|
fp << "\n";
|
|
EmitIndent(fp, indentLevel - 1);
|
|
fp << "}";
|
|
}
|
|
|
|
void ConfigWriter::EmitValue(std::ostream& fp, int indentLevel, const Value& val)
|
|
{
|
|
if (val.IsObjectType<Array>())
|
|
EmitArray(fp, indentLevel, val);
|
|
else if (val.IsObjectType<Dictionary>())
|
|
EmitScope(fp, indentLevel, val);
|
|
else if (val.IsObjectType<ConfigIdentifier>())
|
|
EmitIdentifier(fp, static_cast<ConfigIdentifier::Ptr>(val)->GetName(), false);
|
|
else if (val.IsString())
|
|
EmitString(fp, val);
|
|
else if (val.IsNumber())
|
|
EmitNumber(fp, val);
|
|
else if (val.IsBoolean())
|
|
EmitBoolean(fp, val);
|
|
else if (val.IsEmpty())
|
|
EmitEmpty(fp);
|
|
}
|
|
|
|
void ConfigWriter::EmitRaw(std::ostream& fp, const String& val)
|
|
{
|
|
fp << val;
|
|
}
|
|
|
|
void ConfigWriter::EmitIndent(std::ostream& fp, int indentLevel)
|
|
{
|
|
for (int i = 0; i < indentLevel; i++)
|
|
fp << "\t";
|
|
}
|
|
|
|
void ConfigWriter::EmitIdentifier(std::ostream& fp, const String& identifier, bool inAssignment)
|
|
{
|
|
static std::set<String> keywords;
|
|
static std::mutex mutex;
|
|
|
|
{
|
|
std::unique_lock<std::mutex> lock(mutex);
|
|
if (keywords.empty()) {
|
|
const std::vector<String>& vkeywords = GetKeywords();
|
|
std::copy(vkeywords.begin(), vkeywords.end(), std::inserter(keywords, keywords.begin()));
|
|
}
|
|
}
|
|
|
|
if (keywords.find(identifier) != keywords.end()) {
|
|
fp << "@" << identifier;
|
|
return;
|
|
}
|
|
|
|
boost::regex expr("^[a-zA-Z_][a-zA-Z0-9\\_]*$");
|
|
boost::smatch what;
|
|
if (boost::regex_search(identifier.GetData(), what, expr))
|
|
fp << identifier;
|
|
else if (inAssignment)
|
|
EmitString(fp, identifier);
|
|
else
|
|
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid identifier"));
|
|
}
|
|
|
|
void ConfigWriter::EmitConfigItem(std::ostream& fp, const String& type, const String& name, bool isTemplate,
|
|
bool ignoreOnError, const Array::Ptr& imports, const Dictionary::Ptr& attrs)
|
|
{
|
|
if (isTemplate)
|
|
fp << "template ";
|
|
else
|
|
fp << "object ";
|
|
|
|
EmitIdentifier(fp, type, false);
|
|
fp << " ";
|
|
EmitString(fp, name);
|
|
|
|
if (ignoreOnError)
|
|
fp << " ignore_on_error";
|
|
|
|
fp << " ";
|
|
EmitScope(fp, 1, attrs, imports, true);
|
|
}
|
|
|
|
void ConfigWriter::EmitFunctionCall(std::ostream& fp, const String& name, const Array::Ptr& arguments)
|
|
{
|
|
EmitIdentifier(fp, name, false);
|
|
fp << "(";
|
|
EmitArrayItems(fp, 0, arguments);
|
|
fp << ")";
|
|
}
|
|
|
|
String ConfigWriter::EscapeIcingaString(const String& str)
|
|
{
|
|
String result = str;
|
|
boost::algorithm::replace_all(result, "\\", "\\\\");
|
|
boost::algorithm::replace_all(result, "\n", "\\n");
|
|
boost::algorithm::replace_all(result, "\t", "\\t");
|
|
boost::algorithm::replace_all(result, "\r", "\\r");
|
|
boost::algorithm::replace_all(result, "\b", "\\b");
|
|
boost::algorithm::replace_all(result, "\f", "\\f");
|
|
boost::algorithm::replace_all(result, "\"", "\\\"");
|
|
return result;
|
|
}
|
|
|
|
const std::vector<String>& ConfigWriter::GetKeywords()
|
|
{
|
|
static std::vector<String> keywords;
|
|
static std::mutex mutex;
|
|
std::unique_lock<std::mutex> lock(mutex);
|
|
|
|
if (keywords.empty()) {
|
|
keywords.emplace_back("object");
|
|
keywords.emplace_back("template");
|
|
keywords.emplace_back("include");
|
|
keywords.emplace_back("include_recursive");
|
|
keywords.emplace_back("include_zones");
|
|
keywords.emplace_back("library");
|
|
keywords.emplace_back("null");
|
|
keywords.emplace_back("true");
|
|
keywords.emplace_back("false");
|
|
keywords.emplace_back("const");
|
|
keywords.emplace_back("var");
|
|
keywords.emplace_back("this");
|
|
keywords.emplace_back("globals");
|
|
keywords.emplace_back("locals");
|
|
keywords.emplace_back("use");
|
|
keywords.emplace_back("using");
|
|
keywords.emplace_back("namespace");
|
|
keywords.emplace_back("default");
|
|
keywords.emplace_back("ignore_on_error");
|
|
keywords.emplace_back("current_filename");
|
|
keywords.emplace_back("current_line");
|
|
keywords.emplace_back("apply");
|
|
keywords.emplace_back("to");
|
|
keywords.emplace_back("where");
|
|
keywords.emplace_back("import");
|
|
keywords.emplace_back("assign");
|
|
keywords.emplace_back("ignore");
|
|
keywords.emplace_back("function");
|
|
keywords.emplace_back("return");
|
|
keywords.emplace_back("break");
|
|
keywords.emplace_back("continue");
|
|
keywords.emplace_back("for");
|
|
keywords.emplace_back("if");
|
|
keywords.emplace_back("else");
|
|
keywords.emplace_back("while");
|
|
keywords.emplace_back("throw");
|
|
keywords.emplace_back("try");
|
|
keywords.emplace_back("except");
|
|
}
|
|
|
|
return keywords;
|
|
}
|
|
|
|
ConfigIdentifier::ConfigIdentifier(String identifier)
|
|
: m_Name(std::move(identifier))
|
|
{ }
|
|
|
|
String ConfigIdentifier::GetName() const
|
|
{
|
|
return m_Name;
|
|
}
|