File: scriptframe.cpp

package info (click to toggle)
icinga2 2.15.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 20,040 kB
  • sloc: cpp: 97,870; sql: 3,261; cs: 1,636; yacc: 1,584; sh: 1,009; ansic: 890; lex: 420; python: 80; makefile: 62; javascript: 12
file content (177 lines) | stat: -rw-r--r-- 5,053 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */

#include "base/scriptframe.hpp"
#include "base/scriptglobal.hpp"
#include "base/namespace.hpp"
#include "base/exception.hpp"
#include "base/configuration.hpp"
#include "base/utility.hpp"

using namespace icinga;

boost::thread_specific_ptr<std::stack<ScriptFrame *> > ScriptFrame::m_ScriptFrames;

static Namespace::Ptr l_SystemNS, l_StatsNS;

/* Ensure that this gets called with highest priority
 * and wins against other static initializers in lib/icinga, etc.
 * LTO-enabled builds will cause trouble otherwise, see GH #6575.
 */
INITIALIZE_ONCE_WITH_PRIORITY([]() {
	Namespace::Ptr globalNS = ScriptGlobal::GetGlobals();

	l_SystemNS = new Namespace(true);
	l_SystemNS->Set("PlatformKernel", Utility::GetPlatformKernel());
	l_SystemNS->Set("PlatformKernelVersion", Utility::GetPlatformKernelVersion());
	l_SystemNS->Set("PlatformName", Utility::GetPlatformName());
	l_SystemNS->Set("PlatformVersion", Utility::GetPlatformVersion());
	l_SystemNS->Set("PlatformArchitecture", Utility::GetPlatformArchitecture());
	l_SystemNS->Set("BuildHostName", ICINGA_BUILD_HOST_NAME);
	l_SystemNS->Set("BuildCompilerName", ICINGA_BUILD_COMPILER_NAME);
	l_SystemNS->Set("BuildCompilerVersion", ICINGA_BUILD_COMPILER_VERSION);
	globalNS->Set("System", l_SystemNS, true);

	l_SystemNS->Set("Configuration", new Configuration());

	l_StatsNS = new Namespace(true);
	globalNS->Set("StatsFunctions", l_StatsNS, true);

	globalNS->Set("Internal", new Namespace(), true);
}, InitializePriority::CreateNamespaces);

INITIALIZE_ONCE_WITH_PRIORITY([]() {
	l_SystemNS->Freeze();
	l_StatsNS->Freeze();
}, InitializePriority::FreezeNamespaces);

/**
 * Construct a @c ScriptFrame that has `Self` assigned to the global namespace.
 *
 * Prefer the other constructor if possible since if misused this may leak global variables
 * without permissions or senstive variables like TicketSalt in a sandboxed context.
 *
 * @todo Remove this constructor and call the other with the global namespace in places where it's actually necessary.
 */
ScriptFrame::ScriptFrame(bool allocLocals)
	: Locals(allocLocals ? new Dictionary() : nullptr), PermChecker(new ScriptPermissionChecker),
	  Self(ScriptGlobal::GetGlobals()), Sandboxed(false), Depth(0), Globals(nullptr)
{
	InitializeFrame();
}

ScriptFrame::ScriptFrame(bool allocLocals, Value self)
	: Locals(allocLocals ? new Dictionary() : nullptr), PermChecker(new ScriptPermissionChecker), Self(std::move(self)),
	  Sandboxed(false), Depth(0), Globals(nullptr)
{
	InitializeFrame();
}

void ScriptFrame::InitializeFrame()
{
	std::stack<ScriptFrame *> *frames = m_ScriptFrames.get();

	if (frames && !frames->empty()) {
		ScriptFrame *frame = frames->top();

		// See the documentation of `ScriptFrame::Globals` for why these two are inherited and Globals isn't.
		PermChecker = frame->PermChecker;
		Sandboxed = frame->Sandboxed;
	}

	PushFrame(this);
}

ScriptFrame::~ScriptFrame()
{
	ScriptFrame *frame = PopFrame();
	ASSERT(frame == this);

#ifndef I2_DEBUG
	(void)frame;
#endif /* I2_DEBUG */
}

/**
 * Returns a sanitized copy of the global variables namespace when sandboxed.
 *
 * This filters out the TicketSalt variable specifically and any variable for which the
 * PermChecker does not return 'true'.
 *
 * However it specifically keeps the Types, System, and Icinga sub-namespaces, because they're
 * accessed through globals in ScopeExpression and the user should have access to all Values
 * contained in these namespaces.
 *
 * @return a sanitized copy of the global namespace if sandboxed, a pointer to the global namespace otherwise.
 */
Namespace::Ptr ScriptFrame::GetGlobals()
{
	if (Sandboxed) {
		if (!Globals) {
			Globals = new Namespace;
			auto globals = ScriptGlobal::GetGlobals();
			ObjectLock lock{globals};
			for (auto& [key, val] : globals) {
				if (key == "TicketSalt") {
					continue;
				}

				if (key == "Types" || key == "System" || key == "Icinga" || PermChecker->CanAccessGlobalVariable(key)) {
					Globals->Set(key, val.Val, val.Const);
				}
			}
		}
		return Globals;
	}

	return ScriptGlobal::GetGlobals();
}

void ScriptFrame::IncreaseStackDepth()
{
	if (Depth + 1 > 300)
		BOOST_THROW_EXCEPTION(ScriptError("Stack overflow while evaluating expression: Recursion level too deep."));

	Depth++;
}

void ScriptFrame::DecreaseStackDepth()
{
	Depth--;
}

ScriptFrame *ScriptFrame::GetCurrentFrame()
{
	std::stack<ScriptFrame *> *frames = m_ScriptFrames.get();

	ASSERT(!frames->empty());
	return frames->top();
}

ScriptFrame *ScriptFrame::PopFrame()
{
	std::stack<ScriptFrame *> *frames = m_ScriptFrames.get();

	ASSERT(!frames->empty());

	ScriptFrame *frame = frames->top();
	frames->pop();

	return frame;
}

void ScriptFrame::PushFrame(ScriptFrame *frame)
{
	std::stack<ScriptFrame *> *frames = m_ScriptFrames.get();

	if (!frames) {
		frames = new std::stack<ScriptFrame *>();
		m_ScriptFrames.reset(frames);
	}

	if (!frames->empty()) {
		ScriptFrame *parent = frames->top();
		frame->Depth += parent->Depth;
	}

	frames->push(frame);
}