123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- <?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 ): Command {
- $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' => $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}" );
- }
- }
|