Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 76
SystemdWrapper
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 4
462
0.00% covered (danger)
0.00%
0 / 76
 __construct
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 5
 wrap
0.00% covered (danger)
0.00%
0 / 1
240
0.00% covered (danger)
0.00%
0 / 62
 makeList
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 7
 getPriority
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
<?php
namespace Shellbox\Command;
use Shellbox\Shellbox;
use Shellbox\ShellboxError;
/**
 * A wrapper which runs the command via systemd-run
 */
class SystemdWrapper extends Wrapper {
    /**
     * Systemd needs to be an outer wrapper since it doesn't directly exec the
     * binary and doesn't inherit permissions
     */
    public const PRIORITY = 60;
    public function __construct() {
        parent::__construct();
        if ( !function_exists( 'posix_getuid' ) ) {
            throw new ShellboxError( 'SystemdWrapper requires the posix extension' );
        }
    }
    public function wrap( Command $command ) {
        if ( $command->getDisableSandbox() ) {
            return;
        }
        $args = [ '/usr/bin/systemd-run', '--user', '--pipe', '--quiet', '--no-ask-password' ];
        $wd = $command->getWorkingDirectory();
        if ( $wd !== null ) {
            $args[] = "-pWorkingDirectory=$wd";
        }
        $cpu = $command->getCpuTimeLimit();
        if ( $cpu ) {
            $args[] = "-pLimitCPU=$cpu";
        }
        $mem = $command->getMemoryLimit();
        if ( $mem ) {
            $args[] = "-pMemoryMax=$mem";
            $args[] = "-pMemorySwapMax=$mem";
        }
        $fileSize = $command->getFileSizeLimit();
        if ( $fileSize ) {
            $args[] = "-pLimitFSIZE=$fileSize";
        }
        $paths = $command->getAllowedPaths();
        if ( $paths ) {
            $args[] = '-pReadWritePaths=' . $this->makeList( $paths );
        }
        $paths = $command->getDisallowedPaths();
        if ( $paths ) {
            $args[] = '-pTemporaryFileSystem=' . $this->makeList( $paths );
        }
        if ( $command->getDisableNetwork() ) {
            $args[] = '-pPrivateNetwork=yes';
        }
        $disabledSyscalls = $command->getDisabledSyscalls();
        if ( $disabledSyscalls ) {
            $args[] = '-pSystemCallFilter=~' . $this->makeList( $disabledSyscalls );
        }
        if ( $command->getNoNewPrivs() ) {
            $args[] = '-pNoNewPrivileges=yes';
        }
        if ( $command->getPrivateUserNamespace() ) {
            $args[] = '-pPrivateUsers=yes';
        }
        if ( $command->getPrivateDev() ) {
            $args[] = '-pPrivateDevices=yes';
        }
        foreach ( $command->getEnvironment() as $name => $value ) {
            $args[] = "-E$name=$value";
        }
        if ( in_array( 'execve', $disabledSyscalls ) ) {
            $command->unsafeCommand(
                Shellbox::escape( $args ) . ' ' .
                $command->getCommandString() );
        } else {
            $args[] = '/bin/sh';
            $args[] = '-c';
            $args[] = $command->getCommandString();
            $command->replaceParams( $args );
        }
        $command->environment( [
            'XDG_RUNTIME_DIR' => '/run/user/' . posix_getuid()
        ] );
    }
    /**
     * systemd-run uses space-separated lists as config, an odd convention.
     * Ensure that the list members do not contain spaces before combining them
     * into a string.
     *
     * @param string[] $paths
     * @return string
     * @throws ShellboxError
     */
    private function makeList( $paths ) {
        foreach ( $paths as $path ) {
            if ( strpos( $path, ' ' ) !== false ) {
                throw new ShellboxError( 'SystemdWrapper: property list cannot contain a space' );
            }
        }
        return implode( ' ', $paths );
    }
    public function getPriority() {
        return self::PRIORITY;
    }
}