Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can PHP7 opcached files be distributed without source code?

Tags:

php

php-7

opcache

PHP7 offers a bytecode caching mechanism called opcache. I'd like to know if there is any way to distribute and run the "opcached" version of a PHP script (.bin file extension) without distributing its source code. (I enabled the opcache.file_cache directive in php.ini to obtain the .bin file.)

I assume that when executing a script, PHP7 will check the opcache directory for a .bin file with matching name, timestamp, and maybe even compare a checksum or hash value. If all things match, PHP7 will execute the .bin file instead of parse the .php file. Maybe it is possible to 'trick' PHP into executing the .bin file even when the corresponding .php script is not present?

like image 653
dasup Avatar asked Nov 18 '16 00:11

dasup


People also ask

How does OPcache work in PHP?

OPcache is a caching engine built into PHP. When enabled, it dramatically increases the performance of websites that utilize PHP. From php.net: OPcache improves PHP performance by storing precompiled script bytecode in shared memory, thereby removing the need for PHP to load and parse scripts on each request.

How do you purge OPcache?

To flush PHP Opcache on the PHP-FPM method, you have to send a reload to your PHP-FPM daemon. The reload will clear the Opcache, and when the next request arrives, it will force it to rebuild the cache. You can flush the entire cache of all the websites by reloading the single master.

What does OPcache stand for?

Show activity on this post. Zend Server, a commercial product developed by the Zend Corporation (since aquired by RougeWave) have a feature called OPCache. Stand alone open source PHP also has a feature called OPcache, available since PHP 5.5.


1 Answers

PHP needs to be able to open the file for opcache to be invoked; If it doesn't exist, it can't be loaded ...

Let's look in detail, to see what tricks we might play:

if (!file_handle->filename || !ZCG(enabled) || !accel_startup_ok) {
    /* The Accelerator is disabled, act as if without the Accelerator */
    return accelerator_orig_compile_file(file_handle, type);
#ifdef HAVE_OPCACHE_FILE_CACHE
} else if (ZCG(accel_directives).file_cache_only) {
    return file_cache_compile_file(file_handle, type);
#endif
} else if ((!ZCG(counted) && !ZCSG(accelerator_enabled)) ||
           (ZCSG(restart_in_progress) && accel_restart_is_active())) {
#ifdef HAVE_OPCACHE_FILE_CACHE
    if (ZCG(accel_directives).file_cache) {
        return file_cache_compile_file(file_handle, type);
    }
#endif
    return accelerator_orig_compile_file(file_handle, type);
}

We can see that where the file cache is enabled, it takes precedence over the shared memory cache.

Next, we want to look at file_cache_compile_file:

  1. block signals
  2. protect shared memory
  3. zend_file_cache_script_load

Now we look at zend_file_cache_script_load:

  1. open
  2. read header (layout)
  3. verify magic "OPCACHE"
  4. verify system id
  5. optionally validate timestamp
  6. perform read of cached file
  7. verify checksum

So the first problem we have is that the system id is not unique, but is made up of the following elements:

  1. PHP version
  2. Zend Extension build identifier
  3. Binary identifier, contains the following:
    1. sizeof(char)
    2. sizeof(int)
    3. sizeof(long)
    4. sizeof(size_t)
    5. sizeof(zend_long)
    6. ZEND_MM_ALIGNMENT
  4. If not using a dev version of PHP (unreleased, from git):
    1. ___DATE__ compile date of binary
    2. ___TIME___ compile time of binary

The PHP version and build identifier are required because at least the following may change between versions or builds:

  • integral identifiers for opcodes
  • the layout of internal structures
  • the sequence of instructions the VM expects (details of an existing control structure may change fe. foreach)
  • optimizations performed by opcache (because previous ones may be discovered to be unsafe)

The binary identifier is required because at least the layout of a zval changes with endianess and architecture: Architecture may effect the size of some basic compiler types (long, size_t and so on) as well as the upper and lower limits of those types, while endianess can effect the order of members in the structure, as well as the binary representation of basic compiler types.

Note that rather a lot of effort is expended to identify the current system, that should give you pause for thought ...

Disabling validation of timestamps opcache.validate_timestamps=0 will allow the loading of a file cache entry, even if the current file on the file system is empty.

The checksum included in the header is only to verify the script section of the file (which comes after the header), it doesn't (and can't) include the header where the system identifier, or checksum itself is written.

So you can trick PHP into loading a cached file from another machine by changing the system identifier in the header of the cached file to correspond with the target machines identifier.

Should you ?

For fun perhaps, but as a method of deploying your software, definitely not.

The file cache is not intended for this purpose, loading caches from different architectures and or builds will crash PHP.

like image 74
Joe Watkins Avatar answered Sep 30 '22 03:09

Joe Watkins