From 1a44e9fb96287b2ac11d99fec46f2e134b1b5712 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 1 Apr 2015 22:51:38 +0200 Subject: added article about dynamic loading --- dynamic-loading.html | 253 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 dynamic-loading.html diff --git a/dynamic-loading.html b/dynamic-loading.html new file mode 100644 index 0000000..12ae965 --- /dev/null +++ b/dynamic-loading.html @@ -0,0 +1,253 @@ +

Dynamic loading

+

+ 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. +

+

+ 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. +

+

+ Or you can use dynamic loading. +

+

+ PHP has a builtin dl() + 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. +

+

Why is dl() restricted?

+

+ 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? +

+

+ 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. +

+

+ 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. +

+

Thin loader extension

+

+ 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? +

+

+ 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. +

+

+


+/**
+ *  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;
+    }
+}
+
+

+

+ 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. +

+

+ 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. +

+

+ 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: +

+

+


+<?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...
+
+?>
+
+

+

+ 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. +

+

+ 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: +

+

+


+/**
+ *  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;
+    }
+}
+
+

+

+ The above code would allow PHP scripts to dynamically load PHP extensions, + no matter where they are stored on the system: +

+

+


+<?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();
+
+?>
+
+

+

+ 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! +

-- cgit v1.2.3