summaryrefslogtreecommitdiff
path: root/www/wiki/includes/shell/FirejailCommand.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/includes/shell/FirejailCommand.php')
-rw-r--r--www/wiki/includes/shell/FirejailCommand.php160
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}" );
+ }
+
+}