Issues (1507)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

PHPDaemon/Thread/Master.php (13 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace PHPDaemon\Thread;
3
4
use PHPDaemon\Core\Daemon;
5
use PHPDaemon\Core\Debug;
6
use PHPDaemon\Core\EventLoop;
7
use PHPDaemon\Core\Timer;
8
use PHPDaemon\FS\FileSystem;
9
use PHPDaemon\Structures\StackCallbacks;
10
11
/**
12
 * Implementation of the master thread
13
 *
14
 * @package Core
15
 *
16
 * @author  Vasily Zorin <[email protected]>
17
 */
18
class Master extends Generic
19
{
20
21
    /** @var bool */
22
    public $delayedSigReg = true;
23
    /** @var bool */
24
    public $breakMainLoop = false;
25
    /** @var bool */
26
    public $reload = false;
27
    /** @var int */
28
    public $connCounter = 0;
29
    /** @var Collection */
30
    public $workers;
31
    /** @var Collection */
32
    public $ipcthreads;
33
    /** @var */
34
    public $lastMpmActionTs;
35
    /** @var int */
36
    public $minMpmActionInterval = 1; // in seconds
37
    protected $timerCb;
38
39
    /**
40
     * @var StackCallbacks
41
     */
42
    protected $callbacks;
43
44
    /**
45
     * Runtime of Master process
46
     * @return void
47
     */
48
    protected function run()
49
    {
50
        Daemon::$process = $this;
51
52
        $this->prepareSystemEnv();
53
        class_exists('Timer'); // ensure loading this class
54
        gc_enable();
55
56
        $this->callbacks = new StackCallbacks;
57
58
        /*
59
         * @todo This line must be commented according to current libevent binding implementation.
60
         * May be uncommented in future.
61
         */
62
        //EventLoop::init()
63
64
        if (EventLoop::$instance) {
65
            $this->registerEventSignals();
66
        } else {
67
            $this->registerSignals();
68
        }
69
70
        $this->workers = new Collection();
71
        $this->collections['workers'] = $this->workers;
72
        $this->ipcthreads = new Collection;
73
        $this->collections['ipcthreads'] = $this->ipcthreads;
74
75
        Daemon::$appResolver->preload(true);
76
77
        $this->spawnIPCThread();
78
        $this->spawnWorkers(
79
            min(
80
                Daemon::$config->startworkers->value,
81
                Daemon::$config->maxworkers->value
82
            )
83
        );
84
        $this->timerCb = function ($event) use (&$cbs) {
85
            static $c = 0;
86
87
            ++$c;
88
89
            if ($c > 0xFFFFF) {
90
                $c = 1;
91
            }
92
93
            if (($c % 10 == 0)) {
94
                gc_collect_cycles();
95
            }
96
97
            if (!$this->lastMpmActionTs || ((microtime(true) - $this->lastMpmActionTs) > $this->minMpmActionInterval)) {
98
                $this->callMPM();
99
            }
100
            if ($event) {
101
                $event->timeout();
102
            }
103
        };
104
105
        if (EventLoop::$instance) { // we are using libevent in Master
106
            Timer::add($this->timerCb, 1e6 * Daemon::$config->mpmdelay->value, 'MPM');
107
            EventLoop::$instance->run();
108
        } else { // we are NOT using libevent in Master
109
            $lastTimerCall = microtime(true);
110
            $func = $this->timerCb;
111
            while (!$this->breakMainLoop) {
112
                $this->callbacks->executeAll($this);
113
                if (microtime(true) > $lastTimerCall + Daemon::$config->mpmdelay->value) {
114
                    $func(null);
115
                    $lastTimerCall = microtime(true);
116
                }
117
                $this->sigwait();
118
            }
119
        }
120
    }
121
122
    /**
123
     * Log something
124
     * @param string - Message.
125
     * @param string $message
126
     * @return void
127
     */
128
    public function log($message)
129
    {
130
        Daemon::log('M#' . $this->pid . ' ' . $message);
131
    }
132
133
    /**
134
     * @return int
0 ignored issues
show
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
135
     */
136
    protected function callMPM()
137
    {
138
        $state = Daemon::getStateOfWorkers();
139
        if (isset(Daemon::$config->mpm->value) && is_callable($func = Daemon::$config->mpm->value)) {
0 ignored issues
show
The property mpm does not seem to exist in PHPDaemon\Config\_Object.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
140
            $func($this, $state);
141
        }
142
143
        $upToMinWorkers = Daemon::$config->minworkers->value - $state['alive'];
144
        $upToMaxWorkers = Daemon::$config->maxworkers->value - $state['alive'];
145
        $upToMinSpareWorkers = Daemon::$config->minspareworkers->value - $state['idle'];
146
        if ($upToMinSpareWorkers > $upToMaxWorkers) {
147
            $upToMinSpareWorkers = $upToMaxWorkers;
148
        }
149
        $n = max($upToMinSpareWorkers, $upToMinWorkers);
150
        if ($n > 0) {
151
            //Daemon::log('minspareworkers = '.Daemon::$config->minspareworkers->value);
152
            //Daemon::log('maxworkers = '.Daemon::$config->maxworkers->value);
153
            //Daemon::log('maxspareworkers = '.Daemon::$config->maxspareworkers->value);
154
            //Daemon::log(json_encode($state));
155
            //Daemon::log('upToMinSpareWorkers = ' . $upToMinSpareWorkers . '   upToMinWorkers = ' . $upToMinWorkers);
156
            Daemon::log('Spawning ' . $n . ' worker(s)');
157
            $this->spawnWorkers($n);
158
            return $n;
159
        }
160
161
        $a = ['default' => 0];
162
        if (Daemon::$config->maxspareworkers->value > 0) {
163
            // if MaxSpareWorkers enabled, we have to stop idle workers, keeping in mind the MinWorkers
164
            $a['downToMaxSpareWorkers'] = min(
165
                $state['idle'] - Daemon::$config->maxspareworkers->value, // downToMaxSpareWorkers
166
                $state['alive'] - Daemon::$config->minworkers->value //downToMinWorkers
167
            );
168
        }
169
        $a['downToMaxWorkers'] = $state['alive'] - $state['reloading'] - Daemon::$config->maxworkers->value;
170
        $n = max($a);
171
        if ($n > 0) {
172
            //Daemon::log('down = ' . json_encode($a));
173
            //Daemon::log(json_encode($state));
174
            Daemon::log('Stopping ' . $n . ' worker(s)');
175
            $this->stopWorkers($n);
176
            return -$n;
177
        }
178
        return 0;
179
    }
180
181
    /**
182
     * Setup settings on start.
183
     * @return void
184
     */
185
    protected function prepareSystemEnv()
186
    {
187
        register_shutdown_function(function () {
188
            if ($this->pid != posix_getpid()) {
189
                return;
190
            }
191
            if ($this->shutdown === true) {
192
                return;
193
            }
194
            $this->log('Unexcepted shutdown.');
195
            $this->shutdown();
196
        });
197
198
        posix_setsid();
199
        proc_nice(Daemon::$config->masterpriority->value);
200 View Code Duplication
        if (!Daemon::$config->verbosetty->value) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
201
            fclose(STDIN);
202
            fclose(STDOUT);
203
            fclose(STDERR);
204
        }
205
206
        $this->setTitle(
207
            Daemon::$runName . ': master process'
208
            . (Daemon::$config->pidfile->value !== Daemon::$config->pidfile->defaultValue ? ' (' . Daemon::$config->pidfile->value . ')' : '')
209
        );
210
    }
211
212
    /**
213
     * Reload worker by internal id
214
     * @param integer - Id of worker
215
     * @param integer $id
216
     * @return void
217
     */
218
    public function reloadWorker($id)
219
    {
220
        if (isset($this->workers->threads[$id])) {
221
            if (!$this->workers->threads[$id]->reloaded) {
0 ignored issues
show
The property reloaded does not seem to exist in PHPDaemon\Thread\Generic.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
222
                Daemon::$process->log('Spawning worker-replacer for reloaded worker #' . $id);
223
                $this->spawnWorkers(1);
224
                $this->workers->threads[$id]->reloaded = true;
0 ignored issues
show
The property reloaded does not exist on object<PHPDaemon\Thread\Generic>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
225
            }
226
        }
227
    }
228
229
    /**
230
     * Spawn new worker processes
231
     * @param $n - integer - number of workers to spawn
232
     * @return boolean - success
233
     */
234
    protected function spawnWorkers($n)
235
    {
236
        if (FileSystem::$supported) {
237
            eio_event_loop();
238
        }
239
        $n = (int)$n;
240
241
        for ($i = 0; $i < $n; ++$i) {
242
            $thread = new Worker;
243
            $this->workers->push($thread);
244 View Code Duplication
            $this->callbacks->push(function ($self) use ($thread) {
0 ignored issues
show
The parameter $self is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
245
                // @check - is it possible to run iterate of main event loop without child termination?
246
                $thread->start();
247
                $pid = $thread->getPid();
248
                if ($pid < 0) {
249
                    Daemon::$process->log('could not fork worker');
250
                } elseif ($pid === 0) { // worker
251
                    Daemon::log('Unexcepted execution return to outside of Thread->start()');
252
                    exit;
253
                }
254
            });
255
        }
256
        if ($n > 0) {
257
            $this->lastMpmActionTs = microtime(true);
258
            if (EventLoop::$instance) {
259
                EventLoop::$instance->interrupt();
260
            }
261
        }
262
        return true;
263
    }
264
265
    /**
266
     * Spawn IPC process
267
     * @param $n - integer - number of workers to spawn
268
     * @return boolean - success
269
     */
270
    protected function spawnIPCThread()
271
    {
272
        if (FileSystem::$supported) {
273
            eio_event_loop();
274
        }
275
        $thread = new IPC;
276
        $this->ipcthreads->push($thread);
277
278 View Code Duplication
        $this->callbacks->push(function ($self) use ($thread) {
0 ignored issues
show
The parameter $self is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
279
            $thread->start();
280
            $pid = $thread->getPid();
281
            if ($pid < 0) {
282
                Daemon::$process->log('could not fork IPCThread');
283
            } elseif ($pid === 0) { // worker
284
                $this->log('Unexcepted execution return to outside of Thread->start()');
285
                exit;
286
            }
287
        });
288
        if (EventLoop::$instance) {
289
            EventLoop::$instance->interrupt();
290
        }
291
        return true;
292
    }
293
294
    /**
295
     * Stop the workers
296
     * @param $n - integer - number of workers to stop
297
     * @return boolean - success
298
     */
299
    protected function stopWorkers($n = 1)
300
    {
301
        Daemon::log('--' . $n . '-- ' . Debug::backtrace() . '-----');
302
303
        $n = (int)$n;
304
        $i = 0;
305
306
        foreach ($this->workers->threads as &$w) {
307
            if ($i >= $n) {
308
                break;
309
            }
310
311
            if ($w->shutdown) {
312
                continue;
313
            }
314
315
            if ($w->reloaded) {
0 ignored issues
show
The property reloaded does not seem to exist in PHPDaemon\Thread\Generic.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
316
                continue;
317
            }
318
319
            $w->stop();
320
            ++$i;
321
        }
322
323
        $this->lastMpmActionTs = microtime(true);
324
        return true;
325
    }
326
327
    /**
328
     * Called when master is going to shutdown
329
     * @param integer System singal's number
330
     * @return void
331
     */
332
    protected function shutdown(int $signo = null)
333
    {
334
        if ($signo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $signo of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
335
            $this->signalToChildren($signo);
336
        }
337
        $this->shutdown = true;
338
        $this->waitAll();
339
        Daemon::$shm_wstate->delete();
340
        file_put_contents(Daemon::$config->pidfile->value, '');
341
        posix_kill(getmypid(), SIGKILL);
342
        exit(0);
343
    }
344
345
    /**
346
     * Handler for the SIGCHLD (child is dead) signal in master process.
347
     * @return void
348
     */
349
    protected function sigchld()
350
    {
351
        if (Daemon::$config->logsignals->value) {
352
            $this->log('Caught SIGCHLD.');
353
        }
354
355
        parent::sigchld();
356
    }
357
358
    /**
359
     * Handler for the SIGINT (shutdown) signal in master process. Shutdown.
360
     * @return void
361
     */
362
    protected function sigint()
363
    {
364
        if (Daemon::$config->logsignals->value) {
365
            $this->log('Caught SIGINT.');
366
        }
367
        $this->shutdown(SIGKILL);
368
    }
369
370
    /**
371
     * @param $signo
372
     */
373
    public function signalToChildren($signo)
374
    {
375
        foreach ($this->collections as $col) {
376
            $col->signal($signo);
377
        }
378
    }
379
380
    /**
381
     * Handler for the SIGTERM (shutdown) signal in master process
382
     * @return void
383
     */
384
    protected function sigterm()
385
    {
386
        if (Daemon::$config->logsignals->value) {
387
            $this->log('Caught SIGTERM.');
388
        }
389
390
        $this->shutdown(SIGTERM);
391
    }
392
393
    /**
394
     * Handler for the SIGQUIT signal in master process
395
     * @return void
396
     */
397
    protected function sigquit()
398
    {
399
        if (Daemon::$config->logsignals->value) {
400
            $this->log('Caught SIGQUIT.');
401
        }
402
403
        $this->shutdown(SIGQUIT);
404
    }
405
406
    /**
407
     * Handler for the SIGTSTP (graceful stop all workers) signal in master process
408
     * @return void
409
     */
410
    protected function sigtstp()
411
    {
412
        if (Daemon::$config->logsignals->value) {
413
            $this->log('Caught SIGTSTP (graceful stop all workers).');
414
        }
415
416
        $this->shutdown(SIGTSTP);
417
    }
418
419
    /**
420
     * Handler for the SIGHUP (reload config) signal in master process
421
     * @return void
422
     */
423 View Code Duplication
    protected function sighup()
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
424
    {
425
        if (Daemon::$config->logsignals->value) {
426
            $this->log('Caught SIGHUP (reload config).');
427
        }
428
429
        if (isset(Daemon::$config->configfile)) {
430
            Daemon::loadConfig(Daemon::$config->configfile->value);
431
        }
432
433
        $this->signalToChildren(SIGHUP);
434
    }
435
436
    /**
437
     * Handler for the SIGUSR1 (re-open log-file) signal in master process
438
     * @return void
439
     */
440
    protected function sigusr1()
441
    {
442
        if (Daemon::$config->logsignals->value) {
443
            $this->log('Caught SIGUSR1 (re-open log-file).');
444
        }
445
446
        Daemon::openLogs();
447
        $this->signalToChildren(SIGUSR1);
448
    }
449
450
    /**
451
     * Handler for the SIGUSR2 (graceful restart all workers) signal in master process
452
     * @return void
453
     */
454
    protected function sigusr2()
455
    {
456
        if (Daemon::$config->logsignals->value) {
457
            $this->log('Caught SIGUSR2 (graceful restart all workers).');
458
        }
459
        $this->signalToChildren(SIGUSR2);
460
    }
461
462
    /**
463
     * Handler for the SIGTTIN signal in master process
464
     * Used as "ping" signal
465
     * @return void
466
     */
467
    protected function sigttin()
468
    {
469
    }
470
471
    /**
472
     * Handler for the SIGXSFZ signal in master process
473
     * @return void
474
     */
475
    protected function sigxfsz()
476
    {
477
        $this->log('Caught SIGXFSZ.');
478
    }
479
480
    /**
481
     * Handler for non-known signals
482
     * @return void
483
     */
484 View Code Duplication
    protected function sigunknown($signo)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
485
    {
486
        if (isset(Generic::$signals[$signo])) {
487
            $sig = Generic::$signals[$signo];
488
        } else {
489
            $sig = 'UNKNOWN';
490
        }
491
        $this->log('Caught signal #' . $signo . ' (' . $sig . ').');
492
    }
493
}
494