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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
|
<h1>Dynamic loading</h1>
<p>
Users who switch from PHP to C++ often ask whether it is more difficult
to manage a system if you use C++ code instead of PHP code. We must be honest
here: working with PHP is easier than working with C++. To activate a PHP
script, you for example don't need root access, and you can simply copy the script
to the web server. Deploying a native C++ extension requires more work than that:
you need to stop the webserver first, compile the extension, install it, and
then restart the web server.
</p>
<p>
On top of that, when an extension is deployed it is immediately active for
all websites that are hosted on the webserver. A deployed PHP script only
changes the behavior of a single website, but a deployed C++ extension affects
all sites. It is not really possible to activate an extension for only specific sites,
or to test a new version of an extension for just a single website, as the
extensions are shared by all PHP processes. If you really want to use
different extensions for different sites, you need multiple servers that all
have their own configuration.
</p>
<p>
Or you can use dynamic loading.
</p>
<p>
PHP has a builtin <a href="http://php.net/dl">dl()</a>
function that you can use to load extensions on the fly. This allows you
to call the 'dl("myextension.so")' function from a PHP script to load an
extension, so that an extension only works for a specific site. This
builtin 'dl()' function has some restrictions because of security
considerations (it would otherwise allow users to run arbitrary native code),
but if you're the only one who is in charge of a system, or when a server is not
shared by multiple organizations, you can use PHP-CPP to create a function
similar to 'dl()' that does not have this restriction.
</p>
<h2>Why is dl() restricted?</h2>
<p>
The dl() function is restricted because of security issues. When you use
dl(), you can only load extensions that are stored in the system
wide extensions directory, and not for loading extensions that users have
placed in other locations. A call to dl("/home/user/myextension.so") will
therefore fail, because "/home/user" is not the official extensions
directory. Why this restriction?
</p>
<p>
To understand this, one must first realize that in a normal PHP installation
PHP scripts are edited by users who do not have root access. On shared hosting
environments different users all run their own website on the same system. In
such a setup, it is an absolute no-go if one user can write a script or program
that has access to the data of others. With an unrestricted dl() function
however, exactly this would be possible. An unrestricted dl() call would
allow PHP programmers to write a native extension, store that in their home
directory or in the /tmp directory, and have it loaded by the webserver process.
They could then execute arbitrary code, and possibly
install loggers or other malicious code inside other people's websites. By
only allowing extensions from the system wide extensions directory to be loaded,
PHP ensures every dynamically loaded extension must at least have been installed
by the system administrator.
</p>
<p>
When you write your own extensions however - either directly on top of the Zend
API, or by using the PHP-CPP library - you already are in a position to write and execute
arbitrary code anyway. No need for security checks here. From you C/C++ code
you can do whatever you want. Would it not be cool if you could dynamically
load an extension based on the requirements of a site? One website needs
to be stable, and loads the well tested version 1.0 of your extension, while
a second website is more experimental, and loads version 2.0. You could be
running two versions of your extension on the same machine.
</p>
<h2>Thin loader extension</h2>
<p>
Imagine you're writing your own extension "MyExtension" that has many different
classes and functions, and for which you plan to bring out new releases all the time.
You do not want to deploy a new release in a "big bang" style, but you want to roll out
new versions slowly, one customer or one website at a time. How would you do this?
</p>
<p>
You start by developing a thin loader extension: the ExtensionLoader
extension. This extension has only one function: enable_my_extension() - which
takes the version number of your actual extension, and dynamically loads that version
of your extension. This is a simple extension (it only has one function)
that you install globally, and that you will probably never have to update.
</p>
<p>
<pre class="language-c++"><code>
/**
* Function to load an extension by its version number
*
* It takes one argument: the version number of your extension,
* and returns a boolean to indicate whether the extension was
* correctly loaded.
*
* @param params Vector of parameters
* @return boolean
*/
Php::Value enable_my_extension(Php::Parameters &params)
{
// get version number
int version = params[0];
// construct pathname to your extension (this is for example
// /path/to/MyExtension.so.1 or /path/to/MyExtension.so.2)
std::string path = "/path/to/MyExtension.so." + std::to_string(version);
// load the extension
return Php::dl(path);
}
/**
* Switch to C context to ensure that the get_module() function
* is callable by C programs (which the Zend engine is)
*/
extern "C" {
/**
* Startup function that is called by the Zend engine
* to retrieve all information about the extension
* @return void*
*/
PHPCPP_EXPORT void *get_module() {
// create static instance of the extension object
static Php::Extension myExtension("ExtensionLoader", "1.0");
// the extension has one method
myExtension.add("enable_my_extension", enable_my_extension, {
Php::ByVal("version", Php::Type::Numeric)
});
// return the extension
return myExtension;
}
}
</code></pre>
</p>
<p>
The above code holds the full source code of the ExtensionLoader extension.
You can install this extension on your system, by copy'ing it to the global
php extensions directory and updating the php.ini files.
</p>
<p>
After you've installed this thin loader extension, you can write your actual
big extension full with classes and functions, and compile this extension
into *.so files: the first version you compile into MyExtension.so.1,
and later versions into MyExtension.so.2, MyExtension.so.3, and so on. For every new
release you introduce a new version number, and you copy these shared objects
to the /path/to directory (the same path hardcoded in the 'loader' extension
showed above). And although this is not the official PHP extensions directory,
such extensions can nevertheless be loaded byt the enable_my_extension()
function.
</p>
<p>
You do not have to copy the extensions into the PHP extension directory, nor
do you have to update the php.ini configuration. To activate an extension,
you simply need to call the enable_my_extension() function that was introduced
but he thin loader:
</p>
<p>
<pre class="language-php"><code>
<?php
// enable version 2 of the extension (this will load MyExtension.so.2)
if (!enable_my_extension(2)) die("Version 2 of extension is missing");
// from now on we can use classes and functions from version 2 of the extension
$object = new ClassFromMyExtension();
$object->methodFromMyExtension();
// you get the idea...
?>
</code></pre>
</p>
<p>
The thin loader that we showed above is still pretty secure. It is not possible
to run arbitrary code, or to open arbitrary *.so files. The worst thing that can
happen is that someone opens an extension with a wrong version number - which is
not disastrous at all.
</p>
<p>
But if you really trust the users on your system, you can easily adjust the thin
loader extension to allow other types of parameters too. In the most open
scenario, you could even write a function that allows users to literally
open every possible shared object file:
</p>
<p>
<pre class="language-c++"><code>
/**
* Function to load every possible extension by pathname
*
* It takes one argument: the filename of the PHP extension, and returns a
* boolean to indicate whether the extension was correctly loaded.
*
* This function goes further than the original PHP dl() fuction, because
* it does not check whether the passed in extension object is stored in the
* right directory. Literally every possible extension, also local ones
* created by end users, can be loaded.
*
* @param params Vector of parameters
* @return boolean
*/
Php::Value dl_unrestricted(Php::Parameters &params)
{
// get extension name
std::string pathname = params[0];
// load the extension
return Php::dl(pathname);
}
/**
* Switch to C context to ensure that the get_module() function
* is callable by C programs (which the Zend engine is)
*/
extern "C" {
/**
* Startup function that is called by the Zend engine
* to retrieve all information about the extension
* @return void*
*/
PHPCPP_EXPORT void *get_module() {
// create static instance of the extension object
static Php::Extension myExtension("load_extension", "1.0");
// the extension has one method
myExtension.add("dl_unrestricted", dl_unrestricted, {
Php::ByVal("pathname", Php::Type::String)
});
// return the extension
return myExtension;
}
}
</code></pre>
</p>
<p>
The above code would allow PHP scripts to dynamically load PHP extensions,
no matter where they are stored on the system:
</p>
<p>
<pre class="language-php"><code>
<?php
// load the C++ extension stored in the same directory as this file
if (!dl_unrestricted(__DIR__.'/MyExtension.so')) die("Extension could not be loaded");
// from now on we can use classes and functions from the extension
$object = new ClassFromMyExtension();
$object->methodFromMyExtension();
?>
</code></pre>
</p>
<p>
The dl_unrestricted() function is an awesome function, but be careful here:
if you're the administrator of a shared hosting platform, you definitely
do not want to install it!
</p>
<h2 id="persistent">Persistent extensions</h2>
<p>
A dynamically loaded extension is automatically unloaded at the end of the
page view. If a subsequent pageview also dynamically loads the same extension,
it will start with a completely new and fresh environment. If you want to
write an extension that uses static data or static resources (think of a persistent
database connection, or a worker thread that processes tasks) this may not
always be the desired behavior. You want to keep the database connection active,
or the thread running, also after the extension is unloaded.
</p>
<p>
To overcome this, the Php::dl() function comes with a second boolean parameter
that you can use to specify whether you want to load the extension persistently,
or only for that specific pageview.
</p>
<p>
Notice that if you set this parameter to true, the only thing that persists is
the data <i>in</i> an extension. In subsequent pageviews you will still have to
load the extension to activate the functions and classes in it, even if you had
already loaded the extension persistently in an earlier pageview. But because
the extension was already loaded before, the static data in it (like the database
connection or the thread) is preserved.
</p>
<p>
The dl_unrestricted() function that we demonstrated above can be modified to include
this persistent parameter:
</p>
<p>
<pre class="language-c++"><code>
/**
* Function to load every possible extension by pathname
*
* It takes two arguments: the filename of the PHP extension, and a boolean to
* specify whether the extension data should be kept in memory. It returns a
* boolean to indicate whether the extension was correctly loaded.
*
* This function goes further than the original PHP dl() fuction, because
* it does not check whether the passed in extension object is stored in the
* right directory, and because it allows persistent loading of extensions.
* Literally every possible extension, also local ones created by end users,
* can be loaded.
*
* @param params Vector of parameters
* @return boolean
*/
Php::Value dl_unrestricted(Php::Parameters &params)
{
// get extension name
std::string pathname = params[0];
// persistent setting
bool persistent = params.size() > 1 ? params[1].boolValue() : false;
// load the extension
return Php::dl(pathname, persistent);
}
/**
* Switch to C context to ensure that the get_module() function
* is callable by C programs (which the Zend engine is)
*/
extern "C" {
/**
* Startup function that is called by the Zend engine
* to retrieve all information about the extension
* @return void*
*/
PHPCPP_EXPORT void *get_module() {
// create static instance of the extension object
static Php::Extension myExtension("load_extension", "1.0");
// the extension has one method
myExtension.add("dl_unrestricted", dl_unrestricted, {
Php::ByVal("pathname", Php::Type::String),
Php::ByVal("persistent", Php::Type::Bool, false)
});
// return the extension
return myExtension;
}
}
</code></pre>
</p>
<p>
And this extension allows us to dynamically load extensions, while preserving
persistent data <i>inside</i> the extension:
</p>
<p>
<pre class="language-php"><code>
<?php
// load the C++ extension stored in the same directory as this file, the
// extension is persistently loaded, so it may use persistent data like
// database connections and so on.
if (!dl_unrestricted(__DIR__.'/MyExtension.so', true)) die("Extension could not be loaded");
// from now on we can use classes and functions from the extension
$object = new ClassFromMyExtension();
$object->methodFromMyExtension();
?>
</code></pre>
</p>
|