The www/pecl-pledge,php82 port
pecl82-pledge-2.1.2p1 – PHP wrapper for pledge(2) and unveil(2) (cvsweb github mirror)
Description
This PHP extension adds support for OpenBSD's pledge and unveil system calls. The PHP userland functions pledge() and unveil() are wrappers around the OpenBSD system calls. These functions provide a powerful mechanism to defend the PHP runtime and userland against some common exploits.WWW: https://github.com/tvlooy/php-pledge
Readme
+------------------------------------------------------------------------------- | Running ${PKGSTEM} on OpenBSD +------------------------------------------------------------------------------- This PHP extension adds support for OpenBSD's pledge and unveil system calls. The PHP userland functions pledge() and unveil() are wrappers around the OpenBSD system calls. These functions provide a powerful mechanism to defend the PHP runtime and userland against some common exploits. The theory ========== The pledge(2) system call allows a program to restrict the types of operations the program can do after that point. Unlike other similar systems, pledge is specifically designed for programs that need to use a wide variety of operations on initialization, but a fewer number after initialization (when user input will be accepted). All pledge(2) promises are documented in the pledge(2) manual page. The unveil(2) system call restricts the filesytem view. The first call to unveil(2) restricts the view. Subsequent calls can open it more. To prevent further unveiling, call unveil with no parameters or drop the unveil pledge if the program is pledged. Web SAPI usage ============== Be careful what to pledge/unveil! Using this module can cause a situation of self-denial-of-service. If PHP runs with mod_php, using pledge/unveil impacts an entire Apache child process. If pledge/unveil is used in php_fpm, it will impact the entire process for the whole lifetime of the process, not just one request. You might want to set pm.max_requests = 1 in php_fpm config. Architectural tips ================== Make sure you don't load extensions that you don't need in the web SAPI. For example: PHAR, PCNTL, etc. can be useful for hackers, don't load them. For performance reasons it is a good idea to do as little work as possible in the web SAPI. Jobs can often be scheduled in a queue and run asynchronously from the CLI SAPI. For example processing and resizing uploaded images does not need to run in the web SAPI. Jobs that need to do calls to an external service can fail and should implement retry mechanisms. These can slow down the web SAPI. By using the asynchronous approach, the web SAPI loses functionality. Extensions like PHAR, PCNTL, GD, imagick, curl, ... can be unloaded. Less lines of code become accessible in the web facing part of the website and the attack surface gets smaller. The goal is gaining understanding of exactly what functionality is needed by each use-case, so each use-case can be isolated. Pledge/unveil can then be implemented specifically for each use-case. A php_fpm process can implement pledge/unveil in a safe manner when the pm.max_requests configuration flag is set to 1. This means the process will respawn after each request. The default, and recommended, value for this flag is 0 for endless request processing. Because pledge/unveil affects the process and not just the request, different fpm pools can be configured for each type of work. Especially with unveil the developer can make sure system binaries are unavailable, jobs that don't have to write the filesystem will not be able to do so, jobs that don't have to read user uploaded files will not be able to do so, ... In the web SAPI, avoid getting killed in subsequent requests by checking if a certain file or directory is still available and only call unveil if it is. Eg: if (is_dir('/etc')) { unveil(__DIR__, 'r'); } Limiting network calls is not possible with pledge on a destination basis. But a workaround is to use pf to enforce rules on your fpm users, eg: block out proto {tcp udp} user your_fpm_user pass out proto tcp to $mysql_db port 3306 user your_fpm_user pass out proto tcp to $some_rest_api port 443 user your_fpm_user But again, in the example above network calls can be avoided in the web SAPI if mysql runs on a domain socket and work involving API's is scheduled and processed by a CLI job instead. You can use this technique for CLI jobs as well. Example configuration ===================== You can set promises and unveils in your PHP-FPM config. An simplified httpd example /etc/httpd.conf: chroot "/var/www" server "example.com" { listen on * port 80 root "/htdocs/public" directory index "index.php" # Assets not served by PHP location match "\.(css|gif|jpg|png|js)$" { pass } location match "/specific-path-1" { request rewrite "/index.php/%1" fastcgi socket "/run/php-fpm-specific-path-1.sock" } location match "/specific-path-2" { request rewrite "/index.php/%1" fastcgi socket "/run/php-fpm-specific-path-2.sock" } # The default PHP handler location match "^/(.+)$" { request rewrite "/index.php/%1" fastcgi socket "/run/php-fpm.sock" } } With a simplified PHP-FPM /etc/php-fpm.conf: [global] include=/etc/php-fpm.d/*.conf [specific-path-1] user = www group = www listen.owner = www listen.group = www listen.mode = 0660 pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 chroot = /var/www pm.max_requests = 1 listen = /var/www/run/php-fpm-specific-path-1.sock php_admin_value[openbsd.pledge_promises] = stdio rpath wpath cpath fattr flock unveil php_admin_value[openbsd.unveil] = /:r,/tmp:rwc,/htdocs/var/log:rwc,/htdocs/var/cache:rwc [specific-path-2] user = www group = www listen.owner = www listen.group = www listen.mode = 0660 pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 chroot = /var/www pm.max_requests = 1 listen = /var/www/run/php-fpm-specific-path-2.sock php_admin_value[openbsd.pledge_promises] = stdio rpath wpath cpath fattr flock unveil php_admin_value[openbsd.unveil] = /:r,/tmp:rwc,/htdocs/var/log:rwc,/htdocs/var/cache:rwc [www] user = www group = www listen.owner = www listen.group = www listen.mode = 0660 pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 chroot = /var/www pm.max_requests = 1 listen = /var/www/run/php-fpm.sock php_admin_value[openbsd.pledge_promises] = stdio rpath wpath cpath fattr flock unveil inet php_admin_value[openbsd.unveil] = /:r,/tmp:rwc,/htdocs/var/log:rwc,/htdocs/var/cache:rwc Don't forget to call `unveil(null, null);` in your PHP userland to disallow future unveil calls, or specify null:null as the last argument e.g. php_admin_value[openbsd.unveil] = /:r,/tmp:rwc,/htdocs/var/log:rwc,/htdocs/var/cache:rwc,null:null From the CLI, you can also provide promises or unveils: $ php \ -dopenbsd.unveil='/var/empty:r,null:null' \ -dopenbsd.pledge_promises='stdio dns' \ -r 'echo gethostbyname("openbsd.org");' 199.185.178.80 $ php \ -dopenbsd.unveil='/var/empty:r,null:null' \ -dopenbsd.pledge_promises='stdio error' \ -r 'echo gethostbyname("openbsd.org");' openbsd.org $ php \ -dopenbsd.unveil='/var/empty:r,null:null' \ -dopenbsd.pledge_promises='stdio' \ -r 'echo gethostbyname("openbsd.org");' Abort trap (core dumped) See https://packagist.org/packages/ctors/pledge-symfony-routing for Symfony framework support.
Maintainer
Tom Van Looy
Categories
Build dependencies
Run dependencies
Files
- /etc/php-8.2.sample/pledge.ini
- /etc/php-8.2/pledge.ini
- /usr/local/lib/php-8.2/modules/pledge.so
- /usr/local/share/doc/pkg-readmes/pecl82-pledge
- /usr/local/share/examples/php-8.2/pledge.ini