diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d970bf..944066b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to `php-vips` will be documented in this file. ## master +- report the real reason when FFI startup fails, and support + `ffi.enable=preload` with a new `FFI::preload()` [wadakatu] - better ffi startup diagnostics [ping-localhost] - add setBlock() and setBlockUntrusted() to control operation blocking [jcupitt] diff --git a/README.md b/README.md index e435a8ed..a7d3016a 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,39 @@ to your `composer.json`: } ``` -php-vips does not yet support preloading, so you need to enable FFI globally. -This has some security implications, since anyone who can run php on your -server can use it to call any native library they have access to. +The simplest thing is `ffi.enable=true`, but that lets any PHP code on your +server call any native library it has access to. You can avoid that with +`ffi.enable=preload`, which is PHP's default: FFI is then only usable from code +that PHP loaded at startup via `opcache.preload`, so php-vips can use it but +the rest of your application cannot. -Of course if attackers are running their own PHP code on your webserver you -are probably already toast, unfortunately. +To run php-vips that way, write a preload script: + +```php + $libraryName]); + self::$libraryLoadError = ""; foreach (self::$libraryPaths as $path) { Utils::debugLog("trying path", ["path" => $path]); try { @@ -261,6 +305,7 @@ private static function libraryLoad( Utils::debugLog("success", []); return $library; } catch (\FFI\Exception $e) { + self::$libraryLoadError = $e->getMessage(); Utils::debugLog("init", [ "msg" => "library load failed", "exception" => $e->getMessage() @@ -281,11 +326,6 @@ private static function init(): void if (!extension_loaded("ffi")) { throw new Exception("FFI extension not loaded"); } - $enable = ini_get("ffi.enable"); - if ($enable != "true" && - $enable != "1") { - throw new Exception("ffi.enable set to '$enable', not 'true'"); - } $vips_libname = self::libraryName("libvips", 42); $glib_libname = self::libraryName("libglib-2.0", 0); @@ -315,8 +355,12 @@ private static function init(): void if (!empty(self::$libraryPaths)) { $msg .= " in any of ['" . implode("', '", self::$libraryPaths) . "']"; } - $msg .= ". Make sure that you've installed libvips and that '$vips_libname'"; - $msg .= " is on your system's library search path."; + if (self::$libraryLoadError !== "") { + $msg .= ". The error was: " . self::$libraryLoadError; + } + $msg .= ". Check that libvips is installed, that '$vips_libname' is on"; + $msg .= " your system's library search path, and that FFI is usable"; + $msg .= " (see the ffi.enable notes in the php-vips README)."; throw new Exception($msg); } diff --git a/tests/PreloadTest.php b/tests/PreloadTest.php new file mode 100644 index 00000000..194cb809 --- /dev/null +++ b/tests/PreloadTest.php @@ -0,0 +1,40 @@ +markTestSkipped('no opcache'); + } + + /* opcache.preload can only be set at startup, so we have to use a + * subprocess. + */ + $command = escapeshellarg(PHP_BINARY) . ' -d opcache.enable_cli=1'; + + /* Before php 8.3, preloading as root fails unless you name the user + * to drop to. php warns if you set it when you are not root. + */ + if (function_exists('posix_geteuid') && posix_geteuid() === 0) { + $command .= ' -d opcache.preload_user=root'; + } + + $output = shell_exec( + $command . + ' -d opcache.preload=' . escapeshellarg(__DIR__ . '/preload.php') . + ' -r ' . escapeshellarg( + '$status = opcache_get_status();' . + 'echo in_array("Jcupitt\\Vips\\Image", ' . + '$status["preload_statistics"]["classes"]) ? "preloaded" : "missing";' + ) . ' 2>&1' + ); + + $this->assertStringNotContainsString("Can't preload", $output); + $this->assertStringContainsString('preloaded', $output); + } +} diff --git a/tests/preload.php b/tests/preload.php new file mode 100644 index 00000000..8aea0831 --- /dev/null +++ b/tests/preload.php @@ -0,0 +1,9 @@ +