Coverage Report

Created: 2022-10-14 11:21

/src/php-src/ext/standard/exec.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright (c) The PHP Group                                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to version 3.01 of the PHP license,      |
6
   | that is bundled with this package in the file LICENSE, and is        |
7
   | available through the world-wide-web at the following url:           |
8
   | http://www.php.net/license/3_01.txt                                  |
9
   | If you did not receive a copy of the PHP license and are unable to   |
10
   | obtain it through the world-wide-web, please send a note to          |
11
   | license@php.net so we can mail you a copy immediately.               |
12
   +----------------------------------------------------------------------+
13
   | Author: Rasmus Lerdorf <rasmus@php.net>                              |
14
   |         Ilia Alshanetsky <iliaa@php.net>                             |
15
   +----------------------------------------------------------------------+
16
 */
17
18
#include <stdio.h>
19
#include "php.h"
20
#include <ctype.h>
21
#include "php_string.h"
22
#include "ext/standard/head.h"
23
#include "ext/standard/file.h"
24
#include "basic_functions.h"
25
#include "exec.h"
26
#include "php_globals.h"
27
#include "SAPI.h"
28
29
#if HAVE_SYS_WAIT_H
30
#include <sys/wait.h>
31
#endif
32
33
#include <signal.h>
34
35
#if HAVE_SYS_TYPES_H
36
#include <sys/types.h>
37
#endif
38
#if HAVE_SYS_STAT_H
39
#include <sys/stat.h>
40
#endif
41
#if HAVE_FCNTL_H
42
#include <fcntl.h>
43
#endif
44
45
#if HAVE_UNISTD_H
46
#include <unistd.h>
47
#endif
48
49
#include <limits.h>
50
51
#ifdef PHP_WIN32
52
# include "win32/nice.h"
53
#endif
54
55
static size_t cmd_max_len;
56
57
/* {{{ PHP_MINIT_FUNCTION(exec) */
58
PHP_MINIT_FUNCTION(exec)
59
3.59k
{
60
3.59k
#ifdef _SC_ARG_MAX
61
3.59k
  cmd_max_len = sysconf(_SC_ARG_MAX);
62
3.59k
  if ((size_t)-1 == cmd_max_len) {
63
0
#ifdef _POSIX_ARG_MAX
64
0
    cmd_max_len = _POSIX_ARG_MAX;
65
#else
66
    cmd_max_len = 4096;
67
#endif
68
0
  }
69
#elif defined(ARG_MAX)
70
  cmd_max_len = ARG_MAX;
71
#elif defined(PHP_WIN32)
72
  /* Executed commands will run through cmd.exe. As long as it's the case,
73
    it's just the constant limit.*/
74
  cmd_max_len = 8192;
75
#else
76
  /* This is just an arbitrary value for the fallback case. */
77
  cmd_max_len = 4096;
78
#endif
79
80
3.59k
  return SUCCESS;
81
3.59k
}
82
/* }}} */
83
84
0
static size_t strip_trailing_whitespace(char *buf, size_t bufl) {
85
0
  size_t l = bufl;
86
0
  while (l-- > 0 && isspace(((unsigned char *)buf)[l]));
87
0
  if (l != (bufl - 1)) {
88
0
    bufl = l + 1;
89
0
    buf[bufl] = '\0';
90
0
  }
91
0
  return bufl;
92
0
}
93
94
0
static size_t handle_line(int type, zval *array, char *buf, size_t bufl) {
95
0
  if (type == 1) {
96
0
    PHPWRITE(buf, bufl);
97
0
    if (php_output_get_level() < 1) {
98
0
      sapi_flush();
99
0
    }
100
0
  } else if (type == 2) {
101
0
    bufl = strip_trailing_whitespace(buf, bufl);
102
0
    add_next_index_stringl(array, buf, bufl);
103
0
  }
104
0
  return bufl;
105
0
}
106
107
/* {{{ php_exec
108
 * If type==0, only last line of output is returned (exec)
109
 * If type==1, all lines will be printed and last lined returned (system)
110
 * If type==2, all lines will be saved to given array (exec with &$array)
111
 * If type==3, output will be printed binary, no lines will be saved or returned (passthru)
112
 *
113
 */
114
PHPAPI int php_exec(int type, const char *cmd, zval *array, zval *return_value)
115
0
{
116
0
  FILE *fp;
117
0
  char *buf;
118
0
  int pclose_return;
119
0
  char *b, *d=NULL;
120
0
  php_stream *stream;
121
0
  size_t buflen, bufl = 0;
122
#if PHP_SIGCHILD
123
  void (*sig_handler)() = NULL;
124
#endif
125
126
#if PHP_SIGCHILD
127
  sig_handler = signal (SIGCHLD, SIG_DFL);
128
#endif
129
130
#ifdef PHP_WIN32
131
  fp = VCWD_POPEN(cmd, "rb");
132
#else
133
0
  fp = VCWD_POPEN(cmd, "r");
134
0
#endif
135
0
  if (!fp) {
136
0
    php_error_docref(NULL, E_WARNING, "Unable to fork [%s]", cmd);
137
0
    goto err;
138
0
  }
139
140
0
  stream = php_stream_fopen_from_pipe(fp, "rb");
141
142
0
  buf = (char *) emalloc(EXEC_INPUT_BUF);
143
0
  buflen = EXEC_INPUT_BUF;
144
145
0
  if (type != 3) {
146
0
    b = buf;
147
148
0
    while (php_stream_get_line(stream, b, EXEC_INPUT_BUF, &bufl)) {
149
      /* no new line found, let's read some more */
150
0
      if (b[bufl - 1] != '\n' && !php_stream_eof(stream)) {
151
0
        if (buflen < (bufl + (b - buf) + EXEC_INPUT_BUF)) {
152
0
          bufl += b - buf;
153
0
          buflen = bufl + EXEC_INPUT_BUF;
154
0
          buf = erealloc(buf, buflen);
155
0
          b = buf + bufl;
156
0
        } else {
157
0
          b += bufl;
158
0
        }
159
0
        continue;
160
0
      } else if (b != buf) {
161
0
        bufl += b - buf;
162
0
      }
163
164
0
      bufl = handle_line(type, array, buf, bufl);
165
0
      b = buf;
166
0
    }
167
0
    if (bufl) {
168
0
      if (buf != b) {
169
        /* Process remaining output */
170
0
        bufl = handle_line(type, array, buf, bufl);
171
0
      }
172
173
      /* Return last line from the shell command */
174
0
      bufl = strip_trailing_whitespace(buf, bufl);
175
0
      RETVAL_STRINGL(buf, bufl);
176
0
    } else { /* should return NULL, but for BC we return "" */
177
0
      RETVAL_EMPTY_STRING();
178
0
    }
179
0
  } else {
180
0
    ssize_t read;
181
0
    while ((read = php_stream_read(stream, buf, EXEC_INPUT_BUF)) > 0) {
182
0
      PHPWRITE(buf, read);
183
0
    }
184
0
  }
185
186
0
  pclose_return = php_stream_close(stream);
187
0
  efree(buf);
188
189
0
done:
190
#if PHP_SIGCHILD
191
  if (sig_handler) {
192
    signal(SIGCHLD, sig_handler);
193
  }
194
#endif
195
0
  if (d) {
196
0
    efree(d);
197
0
  }
198
0
  return pclose_return;
199
0
err:
200
0
  pclose_return = -1;
201
0
  RETVAL_FALSE;
202
0
  goto done;
203
0
}
204
/* }}} */
205
206
static void php_exec_ex(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */
207
0
{
208
0
  char *cmd;
209
0
  size_t cmd_len;
210
0
  zval *ret_code=NULL, *ret_array=NULL;
211
0
  int ret;
212
213
0
  ZEND_PARSE_PARAMETERS_START(1, (mode ? 2 : 3))
214
0
    Z_PARAM_STRING(cmd, cmd_len)
215
0
    Z_PARAM_OPTIONAL
216
0
    if (!mode) {
217
0
      Z_PARAM_ZVAL(ret_array)
218
0
    }
219
0
    Z_PARAM_ZVAL(ret_code)
220
0
  ZEND_PARSE_PARAMETERS_END();
221
222
0
  if (!cmd_len) {
223
0
    php_error_docref(NULL, E_WARNING, "Cannot execute a blank command");
224
0
    RETURN_FALSE;
225
0
  }
226
0
  if (strlen(cmd) != cmd_len) {
227
0
    php_error_docref(NULL, E_WARNING, "NULL byte detected. Possible attack");
228
0
    RETURN_FALSE;
229
0
  }
230
231
0
  if (!ret_array) {
232
0
    ret = php_exec(mode, cmd, NULL, return_value);
233
0
  } else {
234
0
    if (Z_TYPE_P(Z_REFVAL_P(ret_array)) == IS_ARRAY) {
235
0
      ZVAL_DEREF(ret_array);
236
0
      SEPARATE_ARRAY(ret_array);
237
0
    } else {
238
0
      ret_array = zend_try_array_init(ret_array);
239
0
      if (!ret_array) {
240
0
        RETURN_THROWS();
241
0
      }
242
0
    }
243
244
0
    ret = php_exec(2, cmd, ret_array, return_value);
245
0
  }
246
0
  if (ret_code) {
247
0
    ZEND_TRY_ASSIGN_REF_LONG(ret_code, ret);
248
0
  }
249
0
}
250
/* }}} */
251
252
/* {{{ proto string|false exec(string command [, array &output [, int &return_value]])
253
   Execute an external program */
254
PHP_FUNCTION(exec)
255
0
{
256
0
  php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
257
0
}
258
/* }}} */
259
260
/* {{{ proto int|false system(string command [, int &return_value])
261
   Execute an external program and display output */
262
PHP_FUNCTION(system)
263
0
{
264
0
  php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
265
0
}
266
/* }}} */
267
268
/* {{{ proto bool passthru(string command [, int &return_value])
269
   Execute an external program and display raw output */
270
PHP_FUNCTION(passthru)
271
0
{
272
0
  php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3);
273
0
}
274
/* }}} */
275
276
/* {{{ php_escape_shell_cmd
277
   Escape all chars that could possibly be used to
278
   break out of a shell command
279
280
   This function emalloc's a string and returns the pointer.
281
   Remember to efree it when done with it.
282
283
   *NOT* safe for binary strings
284
*/
285
PHPAPI zend_string *php_escape_shell_cmd(const char *str)
286
0
{
287
0
  register size_t x, y;
288
0
  size_t l = strlen(str);
289
0
  uint64_t estimate = (2 * (uint64_t)l) + 1;
290
0
  zend_string *cmd;
291
0
#ifndef PHP_WIN32
292
0
  char *p = NULL;
293
0
#endif
294
295
  /* max command line length - two single quotes - \0 byte length */
296
0
  if (l > cmd_max_len - 2 - 1) {
297
0
    php_error_docref(NULL, E_ERROR, "Command exceeds the allowed length of %zu bytes", cmd_max_len);
298
0
    return ZSTR_EMPTY_ALLOC();
299
0
  }
300
301
0
  cmd = zend_string_safe_alloc(2, l, 0, 0);
302
303
0
  for (x = 0, y = 0; x < l; x++) {
304
0
    int mb_len = php_mblen(str + x, (l - x));
305
306
    /* skip non-valid multibyte characters */
307
0
    if (mb_len < 0) {
308
0
      continue;
309
0
    } else if (mb_len > 1) {
310
0
      memcpy(ZSTR_VAL(cmd) + y, str + x, mb_len);
311
0
      y += mb_len;
312
0
      x += mb_len - 1;
313
0
      continue;
314
0
    }
315
316
0
    switch (str[x]) {
317
0
#ifndef PHP_WIN32
318
0
      case '"':
319
0
      case '\'':
320
0
        if (!p && (p = memchr(str + x + 1, str[x], l - x - 1))) {
321
          /* noop */
322
0
        } else if (p && *p == str[x]) {
323
0
          p = NULL;
324
0
        } else {
325
0
          ZSTR_VAL(cmd)[y++] = '\\';
326
0
        }
327
0
        ZSTR_VAL(cmd)[y++] = str[x];
328
0
        break;
329
#else
330
      /* % is Windows specific for environmental variables, ^%PATH% will
331
        output PATH while ^%PATH^% will not. escapeshellcmd->val will escape all % and !.
332
      */
333
      case '%':
334
      case '!':
335
      case '"':
336
      case '\'':
337
#endif
338
0
      case '#': /* This is character-set independent */
339
0
      case '&':
340
0
      case ';':
341
0
      case '`':
342
0
      case '|':
343
0
      case '*':
344
0
      case '?':
345
0
      case '~':
346
0
      case '<':
347
0
      case '>':
348
0
      case '^':
349
0
      case '(':
350
0
      case ')':
351
0
      case '[':
352
0
      case ']':
353
0
      case '{':
354
0
      case '}':
355
0
      case '$':
356
0
      case '\\':
357
0
      case '\x0A': /* excluding these two */
358
0
      case '\xFF':
359
#ifdef PHP_WIN32
360
        ZSTR_VAL(cmd)[y++] = '^';
361
#else
362
0
        ZSTR_VAL(cmd)[y++] = '\\';
363
0
#endif
364
        /* fall-through */
365
0
      default:
366
0
        ZSTR_VAL(cmd)[y++] = str[x];
367
368
0
    }
369
0
  }
370
0
  ZSTR_VAL(cmd)[y] = '\0';
371
372
0
  if (y > cmd_max_len + 1) {
373
0
    php_error_docref(NULL, E_ERROR, "Escaped command exceeds the allowed length of %zu bytes", cmd_max_len);
374
0
    zend_string_release_ex(cmd, 0);
375
0
    return ZSTR_EMPTY_ALLOC();
376
0
  }
377
378
0
  if ((estimate - y) > 4096) {
379
    /* realloc if the estimate was way overill
380
     * Arbitrary cutoff point of 4096 */
381
0
    cmd = zend_string_truncate(cmd, y, 0);
382
0
  }
383
384
0
  ZSTR_LEN(cmd) = y;
385
386
0
  return cmd;
387
0
}
388
/* }}} */
389
390
/* {{{ php_escape_shell_arg
391
 */
392
PHPAPI zend_string *php_escape_shell_arg(const char *str)
393
0
{
394
0
  size_t x, y = 0;
395
0
  size_t l = strlen(str);
396
0
  zend_string *cmd;
397
0
  uint64_t estimate = (4 * (uint64_t)l) + 3;
398
399
  /* max command line length - two single quotes - \0 byte length */
400
0
  if (l > cmd_max_len - 2 - 1) {
401
0
    php_error_docref(NULL, E_ERROR, "Argument exceeds the allowed length of %zu bytes", cmd_max_len);
402
0
    return ZSTR_EMPTY_ALLOC();
403
0
  }
404
405
0
  cmd = zend_string_safe_alloc(4, l, 2, 0); /* worst case */
406
407
#ifdef PHP_WIN32
408
  ZSTR_VAL(cmd)[y++] = '"';
409
#else
410
0
  ZSTR_VAL(cmd)[y++] = '\'';
411
0
#endif
412
413
0
  for (x = 0; x < l; x++) {
414
0
    int mb_len = php_mblen(str + x, (l - x));
415
416
    /* skip non-valid multibyte characters */
417
0
    if (mb_len < 0) {
418
0
      continue;
419
0
    } else if (mb_len > 1) {
420
0
      memcpy(ZSTR_VAL(cmd) + y, str + x, mb_len);
421
0
      y += mb_len;
422
0
      x += mb_len - 1;
423
0
      continue;
424
0
    }
425
426
0
    switch (str[x]) {
427
#ifdef PHP_WIN32
428
    case '"':
429
    case '%':
430
    case '!':
431
      ZSTR_VAL(cmd)[y++] = ' ';
432
      break;
433
#else
434
0
    case '\'':
435
0
      ZSTR_VAL(cmd)[y++] = '\'';
436
0
      ZSTR_VAL(cmd)[y++] = '\\';
437
0
      ZSTR_VAL(cmd)[y++] = '\'';
438
0
#endif
439
      /* fall-through */
440
0
    default:
441
0
      ZSTR_VAL(cmd)[y++] = str[x];
442
0
    }
443
0
  }
444
#ifdef PHP_WIN32
445
  if (y > 0 && '\\' == ZSTR_VAL(cmd)[y - 1]) {
446
    int k = 0, n = y - 1;
447
    for (; n >= 0 && '\\' == ZSTR_VAL(cmd)[n]; n--, k++);
448
    if (k % 2) {
449
      ZSTR_VAL(cmd)[y++] = '\\';
450
    }
451
  }
452
453
  ZSTR_VAL(cmd)[y++] = '"';
454
#else
455
0
  ZSTR_VAL(cmd)[y++] = '\'';
456
0
#endif
457
0
  ZSTR_VAL(cmd)[y] = '\0';
458
459
0
  if (y > cmd_max_len + 1) {
460
0
    php_error_docref(NULL, E_ERROR, "Escaped argument exceeds the allowed length of %zu bytes", cmd_max_len);
461
0
    zend_string_release_ex(cmd, 0);
462
0
    return ZSTR_EMPTY_ALLOC();
463
0
  }
464
465
0
  if ((estimate - y) > 4096) {
466
    /* realloc if the estimate was way overill
467
     * Arbitrary cutoff point of 4096 */
468
0
    cmd = zend_string_truncate(cmd, y, 0);
469
0
  }
470
0
  ZSTR_LEN(cmd) = y;
471
0
  return cmd;
472
0
}
473
/* }}} */
474
475
/* {{{ proto string escapeshellcmd(string command)
476
   Escape shell metacharacters */
477
PHP_FUNCTION(escapeshellcmd)
478
0
{
479
0
  char *command;
480
0
  size_t command_len;
481
482
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
483
0
    Z_PARAM_STRING(command, command_len)
484
0
  ZEND_PARSE_PARAMETERS_END();
485
486
0
  if (command_len) {
487
0
    if (command_len != strlen(command)) {
488
0
      zend_argument_type_error(1, "must not contain any null bytes");
489
0
      RETURN_THROWS();
490
0
    }
491
0
    RETVAL_STR(php_escape_shell_cmd(command));
492
0
  } else {
493
0
    RETVAL_EMPTY_STRING();
494
0
  }
495
0
}
496
/* }}} */
497
498
/* {{{ proto string escapeshellarg(string arg)
499
   Quote and escape an argument for use in a shell command */
500
PHP_FUNCTION(escapeshellarg)
501
0
{
502
0
  char *argument;
503
0
  size_t argument_len;
504
505
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
506
0
    Z_PARAM_STRING(argument, argument_len)
507
0
  ZEND_PARSE_PARAMETERS_END();
508
509
0
  if (argument_len != strlen(argument)) {
510
0
    zend_argument_type_error(1, "must not contain any null bytes");
511
0
    RETURN_THROWS();
512
0
  }
513
514
0
  RETVAL_STR(php_escape_shell_arg(argument));
515
0
}
516
/* }}} */
517
518
/* {{{ proto string|false shell_exec(string cmd)
519
   Execute command via shell and return complete output as string */
520
PHP_FUNCTION(shell_exec)
521
0
{
522
0
  FILE *in;
523
0
  char *command;
524
0
  size_t command_len;
525
0
  zend_string *ret;
526
0
  php_stream *stream;
527
528
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
529
0
    Z_PARAM_STRING(command, command_len)
530
0
  ZEND_PARSE_PARAMETERS_END();
531
532
0
  if (!command_len) {
533
0
    php_error_docref(NULL, E_WARNING, "Cannot execute a blank command");
534
0
    RETURN_FALSE;
535
0
  }
536
0
  if (strlen(command) != command_len) {
537
0
    php_error_docref(NULL, E_WARNING, "NULL byte detected. Possible attack");
538
0
    RETURN_FALSE;
539
0
  }
540
541
#ifdef PHP_WIN32
542
  if ((in=VCWD_POPEN(command, "rt"))==NULL) {
543
#else
544
0
  if ((in=VCWD_POPEN(command, "r"))==NULL) {
545
0
#endif
546
0
    php_error_docref(NULL, E_WARNING, "Unable to execute '%s'", command);
547
0
    RETURN_FALSE;
548
0
  }
549
550
0
  stream = php_stream_fopen_from_pipe(in, "rb");
551
0
  ret = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0);
552
0
  php_stream_close(stream);
553
554
0
  if (ret && ZSTR_LEN(ret) > 0) {
555
0
    RETVAL_STR(ret);
556
0
  }
557
0
}
558
/* }}} */
559
560
#ifdef HAVE_NICE
561
/* {{{ proto bool proc_nice(int priority)
562
   Change the priority of the current process */
563
PHP_FUNCTION(proc_nice)
564
0
{
565
0
  zend_long pri;
566
567
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
568
0
    Z_PARAM_LONG(pri)
569
0
  ZEND_PARSE_PARAMETERS_END();
570
571
0
  errno = 0;
572
0
  php_ignore_value(nice(pri));
573
0
  if (errno) {
574
#ifdef PHP_WIN32
575
    char *err = php_win_err();
576
    php_error_docref(NULL, E_WARNING, "%s", err);
577
    php_win_err_free(err);
578
#else
579
0
    php_error_docref(NULL, E_WARNING, "Only a super user may attempt to increase the priority of a process");
580
0
#endif
581
0
    RETURN_FALSE;
582
0
  }
583
584
0
  RETURN_TRUE;
585
0
}
586
/* }}} */
587
#endif