MediaWiki REL1_31
FirejailCommand.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Shell;
22
23use RuntimeException;
24
31class FirejailCommand extends Command {
32
36 private $firejail;
37
41 private $whitelistedPaths = [];
42
46 public function __construct( $firejail ) {
47 parent::__construct();
48 $this->firejail = $firejail;
49 }
50
58 public function params( ...$args ): Command {
59 if ( count( $args ) === 1 && is_array( reset( $args ) ) ) {
60 // If only one argument has been passed, and that argument is an array,
61 // treat it as a list of arguments
62 $args = reset( $args );
63 }
64 foreach ( $args as $arg ) {
65 if ( substr( $arg, 0, 8 ) === '--output' ) {
66 $ex = new RuntimeException(
67 'FirejailCommand does not support parameters that start with --output'
68 );
69 $this->logger->error(
70 'command tried to shell out with a parameter starting with --output',
71 [
72 'arg' => $arg,
73 'exception' => $ex
74 ]
75 );
76 throw $ex;
77 }
78 }
79
80 return parent::params( ...$args );
81 }
82
86 public function whitelistPaths( array $paths ) {
87 $this->whitelistedPaths = array_merge( $this->whitelistedPaths, $paths );
88 return $this;
89 }
90
94 protected function buildFinalCommand( $command ) {
95 // If there are no restrictions, don't use firejail
96 if ( $this->restrictions === 0 ) {
97 $splitCommand = explode( ' ', $command, 2 );
98 $this->logger->debug(
99 "firejail: Command {$splitCommand[0]} {params} has no restrictions",
100 [ 'params' => isset( $splitCommand[1] ) ? $splitCommand[1] : '' ]
101 );
102 return parent::buildFinalCommand( $command );
103 }
104
105 if ( $this->firejail === false ) {
106 throw new RuntimeException( 'firejail is enabled, but cannot be found' );
107 }
108 // quiet has to come first to prevent firejail from adding
109 // any output.
110 $cmd = [ $this->firejail, '--quiet' ];
111 // Use a profile that allows people to add local overrides
112 // if their system is setup in an incompatible manner. Also it
113 // prevents any default profiles from running.
114 // FIXME: Doesn't actually override command-line switches?
115 $cmd[] = '--profile=' . __DIR__ . '/firejail.profile';
116
117 // By default firejail hides all other user directories, so if
118 // MediaWiki is inside a home directory (/home) but not the
119 // current user's home directory, pass --allusers to whitelist
120 // the home directories again.
121 static $useAllUsers = null;
122 if ( $useAllUsers === null ) {
123 global $IP;
124 // In case people are doing funny things with symlinks
125 // or relative paths, resolve them all.
126 $realIP = realpath( $IP );
127 $currentUser = posix_getpwuid( posix_geteuid() );
128 $useAllUsers = ( strpos( $realIP, '/home/' ) === 0 )
129 && ( strpos( $realIP, $currentUser['dir'] ) !== 0 );
130 if ( $useAllUsers ) {
131 $this->logger->warning( 'firejail: MediaWiki is located ' .
132 'in a home directory that does not belong to the ' .
133 'current user, so allowing access to all home ' .
134 'directories (--allusers)' );
135 }
136 }
137
138 if ( $useAllUsers ) {
139 $cmd[] = '--allusers';
140 }
141
142 if ( $this->whitelistedPaths ) {
143 // Always whitelist limit.sh
144 $cmd[] = '--whitelist=' . __DIR__ . '/limit.sh';
145 foreach ( $this->whitelistedPaths as $whitelistedPath ) {
146 $cmd[] = "--whitelist={$whitelistedPath}";
147 }
148 }
149
150 if ( $this->hasRestriction( Shell::NO_LOCALSETTINGS ) ) {
151 $cmd[] = '--blacklist=' . realpath( MW_CONFIG_FILE );
152 }
153
154 if ( $this->hasRestriction( Shell::NO_ROOT ) ) {
155 $cmd[] = '--noroot';
156 }
157
158 $useSeccomp = $this->hasRestriction( Shell::SECCOMP );
159 $extraSeccomp = [];
160
161 if ( $this->hasRestriction( Shell::NO_EXECVE ) ) {
162 $extraSeccomp[] = 'execve';
163 // Normally firejail will run commands in a bash shell,
164 // but that won't work if we ban the execve syscall, so
165 // run the command without a shell.
166 $cmd[] = '--shell=none';
167 }
168
169 if ( $useSeccomp ) {
170 $seccomp = '--seccomp';
171 if ( $extraSeccomp ) {
172 // The "@default" seccomp group will always be enabled
173 $seccomp .= '=' . implode( ',', $extraSeccomp );
174 }
175 $cmd[] = $seccomp;
176 }
177
178 if ( $this->hasRestriction( Shell::PRIVATE_DEV ) ) {
179 $cmd[] = '--private-dev';
180 }
181
182 if ( $this->hasRestriction( Shell::NO_NETWORK ) ) {
183 $cmd[] = '--net=none';
184 }
185
186 $builtCmd = implode( ' ', $cmd );
187
188 // Prefix the firejail command in front of the wanted command
189 return parent::buildFinalCommand( "$builtCmd -- {$command}" );
190 }
191
192}
$command
Definition cdb.php:65
if( $line===false) $args
Definition cdb.php:64
Class used for executing shell commands.
Definition Command.php:35
Restricts execution of shell commands using firejail.
buildFinalCommand( $command)
@inheritDoc
whitelistPaths(array $paths)
@inheritDoc
params(... $args)
Reject any parameters that start with –output to prevent exploitation of a firejail RCE (CVE-2020-173...
string $firejail
Path to firejail.
const NO_EXECVE
Deny execve syscall with seccomp.
Definition Shell.php:95
const SECCOMP
Use seccomp to block dangerous syscalls.
Definition Shell.php:72
const NO_NETWORK
Restrict the request to have no network access.
Definition Shell.php:87
const PRIVATE_DEV
Create a private /dev.
Definition Shell.php:79
const NO_ROOT
Disallow any root access.
Definition Shell.php:64
const NO_LOCALSETTINGS
Deny access to LocalSettings.php (MW_CONFIG_FILE)
Definition Shell.php:102
$IP
Definition update.php:3
Copyright (C) 2017 Kunal Mehta legoktm@member.fsf.org
Definition Command.php:21