diff options
Diffstat (limited to 'www/wiki/includes/shell/FirejailCommand.php')
-rw-r--r-- | www/wiki/includes/shell/FirejailCommand.php | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/www/wiki/includes/shell/FirejailCommand.php b/www/wiki/includes/shell/FirejailCommand.php new file mode 100644 index 00000000..d8189304 --- /dev/null +++ b/www/wiki/includes/shell/FirejailCommand.php @@ -0,0 +1,160 @@ +<?php +/** + * Copyright (C) 2017 Kunal Mehta <legoktm@member.fsf.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +namespace MediaWiki\Shell; + +use RuntimeException; + +/** + * Restricts execution of shell commands using firejail + * + * @see https://firejail.wordpress.com/ + * @since 1.31 + */ +class FirejailCommand extends Command { + + /** + * @var string Path to firejail + */ + private $firejail; + + /** + * @var string[] + */ + private $whitelistedPaths = []; + + /** + * @param string $firejail Path to firejail + */ + public function __construct( $firejail ) { + parent::__construct(); + $this->firejail = $firejail; + } + + /** + * @inheritDoc + */ + public function whitelistPaths( array $paths ) { + $this->whitelistedPaths = array_merge( $this->whitelistedPaths, $paths ); + return $this; + } + + /** + * @inheritDoc + */ + protected function buildFinalCommand( $command ) { + // If there are no restrictions, don't use firejail + if ( $this->restrictions === 0 ) { + $splitCommand = explode( ' ', $command, 2 ); + $this->logger->debug( + "firejail: Command {$splitCommand[0]} {params} has no restrictions", + [ 'params' => isset( $splitCommand[1] ) ? $splitCommand[1] : '' ] + ); + return parent::buildFinalCommand( $command ); + } + + if ( $this->firejail === false ) { + throw new RuntimeException( 'firejail is enabled, but cannot be found' ); + } + // quiet has to come first to prevent firejail from adding + // any output. + $cmd = [ $this->firejail, '--quiet' ]; + // Use a profile that allows people to add local overrides + // if their system is setup in an incompatible manner. Also it + // prevents any default profiles from running. + // FIXME: Doesn't actually override command-line switches? + $cmd[] = '--profile=' . __DIR__ . '/firejail.profile'; + + // By default firejail hides all other user directories, so if + // MediaWiki is inside a home directory (/home) but not the + // current user's home directory, pass --allusers to whitelist + // the home directories again. + static $useAllUsers = null; + if ( $useAllUsers === null ) { + global $IP; + // In case people are doing funny things with symlinks + // or relative paths, resolve them all. + $realIP = realpath( $IP ); + $currentUser = posix_getpwuid( posix_geteuid() ); + $useAllUsers = ( strpos( $realIP, '/home/' ) === 0 ) + && ( strpos( $realIP, $currentUser['dir'] ) !== 0 ); + if ( $useAllUsers ) { + $this->logger->warning( 'firejail: MediaWiki is located ' . + 'in a home directory that does not belong to the ' . + 'current user, so allowing access to all home ' . + 'directories (--allusers)' ); + } + } + + if ( $useAllUsers ) { + $cmd[] = '--allusers'; + } + + if ( $this->whitelistedPaths ) { + // Always whitelist limit.sh + $cmd[] = '--whitelist=' . __DIR__ . '/limit.sh'; + foreach ( $this->whitelistedPaths as $whitelistedPath ) { + $cmd[] = "--whitelist={$whitelistedPath}"; + } + } + + if ( $this->hasRestriction( Shell::NO_LOCALSETTINGS ) ) { + $cmd[] = '--blacklist=' . realpath( MW_CONFIG_FILE ); + } + + if ( $this->hasRestriction( Shell::NO_ROOT ) ) { + $cmd[] = '--noroot'; + } + + $useSeccomp = $this->hasRestriction( Shell::SECCOMP ); + $extraSeccomp = []; + + if ( $this->hasRestriction( Shell::NO_EXECVE ) ) { + $extraSeccomp[] = 'execve'; + // Normally firejail will run commands in a bash shell, + // but that won't work if we ban the execve syscall, so + // run the command without a shell. + $cmd[] = '--shell=none'; + } + + if ( $useSeccomp ) { + $seccomp = '--seccomp'; + if ( $extraSeccomp ) { + // The "@default" seccomp group will always be enabled + $seccomp .= '=' . implode( ',', $extraSeccomp ); + } + $cmd[] = $seccomp; + } + + if ( $this->hasRestriction( Shell::PRIVATE_DEV ) ) { + $cmd[] = '--private-dev'; + } + + if ( $this->hasRestriction( Shell::NO_NETWORK ) ) { + $cmd[] = '--net=none'; + } + + $builtCmd = implode( ' ', $cmd ); + + // Prefix the firejail command in front of the wanted command + return parent::buildFinalCommand( "$builtCmd -- {$command}" ); + } + +} |