Coverage Report

Created: 2022-10-06 21:30

/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
6.83k
{
60
6.83k
#ifdef _SC_ARG_MAX
61
6.83k
  cmd_max_len = sysconf(_SC_ARG_MAX);
62
6.83k
  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
6.83k
  return SUCCESS;
81
6.83k
}
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
/* {{{ Execute an external program */
253
PHP_FUNCTION(exec)
254
0
{
255
0
  php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
256
0
}
257
/* }}} */
258
259
/* {{{ Execute an external program and display output */
260
PHP_FUNCTION(system)
261
0
{
262
0
  php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
263
0
}
264
/* }}} */
265
266
/* {{{ Execute an external program and display raw output */
267
PHP_FUNCTION(passthru)
268
0
{
269
0
  php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3);
270
0
}
271
/* }}} */
272
273
/* {{{ php_escape_shell_cmd
274
   Escape all chars that could possibly be used to
275
   break out of a shell command
276
277
   This function emalloc's a string and returns the pointer.
278
   Remember to efree it when done with it.
279
280
   *NOT* safe for binary strings
281
*/
282
PHPAPI zend_string *php_escape_shell_cmd(const char *str)
283
0
{
284
0
  register size_t x, y;
285
0
  size_t l = strlen(str);
286
0
  uint64_t estimate = (2 * (uint64_t)l) + 1;
287
0
  zend_string *cmd;
288
0
#ifndef PHP_WIN32
289
0
  char *p = NULL;
290
0
#endif
291
292
  /* max command line length - two single quotes - \0 byte length */
293
0
  if (l > cmd_max_len - 2 - 1) {
294
0
    php_error_docref(NULL, E_ERROR, "Command exceeds the allowed length of %zu bytes", cmd_max_len);
295
0
    return ZSTR_EMPTY_ALLOC();
296
0
  }
297
298
0
  cmd = zend_string_safe_alloc(2, l, 0, 0);
299
300
0
  for (x = 0, y = 0; x < l; x++) {
301
0
    int mb_len = php_mblen(str + x, (l - x));
302
303
    /* skip non-valid multibyte characters */
304
0
    if (mb_len < 0) {
305
0
      continue;
306
0
    } else if (mb_len > 1) {
307
0
      memcpy(ZSTR_VAL(cmd) + y, str + x, mb_len);
308
0
      y += mb_len;
309
0
      x += mb_len - 1;
310
0
      continue;
311
0
    }
312
313
0
    switch (str[x]) {
314
0
#ifndef PHP_WIN32
315
0
      case '"':
316
0
      case '\'':
317
0
        if (!p && (p = memchr(str + x + 1, str[x], l - x - 1))) {
318
          /* noop */
319
0
        } else if (p && *p == str[x]) {
320
0
          p = NULL;
321
0
        } else {
322
0
          ZSTR_VAL(cmd)[y++] = '\\';
323
0
        }
324
0
        ZSTR_VAL(cmd)[y++] = str[x];
325
0
        break;
326
#else
327
      /* % is Windows specific for environmental variables, ^%PATH% will
328
        output PATH while ^%PATH^% will not. escapeshellcmd->val will escape all % and !.
329
      */
330
      case '%':
331
      case '!':
332
      case '"':
333
      case '\'':
334
#endif
335
0
      case '#': /* This is character-set independent */
336
0
      case '&':
337
0
      case ';':
338
0
      case '`':
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 '\x0A': /* excluding these two */
355
0
      case '\xFF':
356
#ifdef PHP_WIN32
357
        ZSTR_VAL(cmd)[y++] = '^';
358
#else
359
0
        ZSTR_VAL(cmd)[y++] = '\\';
360
0
#endif
361
        /* fall-through */
362
0
      default:
363
0
        ZSTR_VAL(cmd)[y++] = str[x];
364
365
0
    }
366
0
  }
367
0
  ZSTR_VAL(cmd)[y] = '\0';
368
369
0
  if (y > cmd_max_len + 1) {
370
0
    php_error_docref(NULL, E_ERROR, "Escaped command exceeds the allowed length of %zu bytes", cmd_max_len);
371
0
    zend_string_release_ex(cmd, 0);
372
0
    return ZSTR_EMPTY_ALLOC();
373
0
  }
374
375
0
  if ((estimate - y) > 4096) {
376
    /* realloc if the estimate was way overill
377
     * Arbitrary cutoff point of 4096 */
378
0
    cmd = zend_string_truncate(cmd, y, 0);
379
0
  }
380
381
0
  ZSTR_LEN(cmd) = y;
382
383
0
  return cmd;
384
0
}
385
/* }}} */
386
387
/* {{{ php_escape_shell_arg */
388
PHPAPI zend_string *php_escape_shell_arg(const char *str)
389
0
{
390
0
  size_t x, y = 0;
391
0
  size_t l = strlen(str);
392
0
  zend_string *cmd;
393
0
  uint64_t estimate = (4 * (uint64_t)l) + 3;
394
395
  /* max command line length - two single quotes - \0 byte length */
396
0
  if (l > cmd_max_len - 2 - 1) {
397
0
    php_error_docref(NULL, E_ERROR, "Argument exceeds the allowed length of %zu bytes", cmd_max_len);
398
0
    return ZSTR_EMPTY_ALLOC();
399
0
  }
400
401
0
  cmd = zend_string_safe_alloc(4, l, 2, 0); /* worst case */
402
403
#ifdef PHP_WIN32
404
  ZSTR_VAL(cmd)[y++] = '"';
405
#else
406
0
  ZSTR_VAL(cmd)[y++] = '\'';
407
0
#endif
408
409
0
  for (x = 0; x < l; x++) {
410
0
    int mb_len = php_mblen(str + x, (l - x));
411
412
    /* skip non-valid multibyte characters */
413
0
    if (mb_len < 0) {
414
0
      continue;
415
0
    } else if (mb_len > 1) {
416
0
      memcpy(ZSTR_VAL(cmd) + y, str + x, mb_len);
417
0
      y += mb_len;
418
0
      x += mb_len - 1;
419
0
      continue;
420
0
    }
421
422
0
    switch (str[x]) {
423
#ifdef PHP_WIN32
424
    case '"':
425
    case '%':
426
    case '!':
427
      ZSTR_VAL(cmd)[y++] = ' ';
428
      break;
429
#else
430
0
    case '\'':
431
0
      ZSTR_VAL(cmd)[y++] = '\'';
432
0
      ZSTR_VAL(cmd)[y++] = '\\';
433
0
      ZSTR_VAL(cmd)[y++] = '\'';
434
0
#endif
435
      /* fall-through */
436
0
    default:
437
0
      ZSTR_VAL(cmd)[y++] = str[x];
438
0
    }
439
0
  }
440
#ifdef PHP_WIN32
441
  if (y > 0 && '\\' == ZSTR_VAL(cmd)[y - 1]) {
442
    int k = 0, n = y - 1;
443
    for (; n >= 0 && '\\' == ZSTR_VAL(cmd)[n]; n--, k++);
444
    if (k % 2) {
445
      ZSTR_VAL(cmd)[y++] = '\\';
446
    }
447
  }
448
449
  ZSTR_VAL(cmd)[y++] = '"';
450
#else
451
0
  ZSTR_VAL(cmd)[y++] = '\'';
452
0
#endif
453
0
  ZSTR_VAL(cmd)[y] = '\0';
454
455
0
  if (y > cmd_max_len + 1) {
456
0
    php_error_docref(NULL, E_ERROR, "Escaped argument exceeds the allowed length of %zu bytes", cmd_max_len);
457
0
    zend_string_release_ex(cmd, 0);
458
0
    return ZSTR_EMPTY_ALLOC();
459
0
  }
460
461
0
  if ((estimate - y) > 4096) {
462
    /* realloc if the estimate was way overill
463
     * Arbitrary cutoff point of 4096 */
464
0
    cmd = zend_string_truncate(cmd, y, 0);
465
0
  }
466
0
  ZSTR_LEN(cmd) = y;
467
0
  return cmd;
468
0
}
469
/* }}} */
470
471
/* {{{ Escape shell metacharacters */
472
PHP_FUNCTION(escapeshellcmd)
473
0
{
474
0
  char *command;
475
0
  size_t command_len;
476
477
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
478
0
    Z_PARAM_STRING(command, command_len)
479
0
  ZEND_PARSE_PARAMETERS_END();
480
481
0
  if (command_len) {
482
0
    if (command_len != strlen(command)) {
483
0
      zend_argument_type_error(1, "must not contain any null bytes");
484
0
      RETURN_THROWS();
485
0
    }
486
0
    RETVAL_STR(php_escape_shell_cmd(command));
487
0
  } else {
488
0
    RETVAL_EMPTY_STRING();
489
0
  }
490
0
}
491
/* }}} */
492
493
/* {{{ Quote and escape an argument for use in a shell command */
494
PHP_FUNCTION(escapeshellarg)
495
0
{
496
0
  char *argument;
497
0
  size_t argument_len;
498
499
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
500
0
    Z_PARAM_STRING(argument, argument_len)
501
0
  ZEND_PARSE_PARAMETERS_END();
502
503
0
  if (argument_len != strlen(argument)) {
504
0
    zend_argument_type_error(1, "must not contain any null bytes");
505
0
    RETURN_THROWS();
506
0
  }
507
508
0
  RETVAL_STR(php_escape_shell_arg(argument));
509
0
}
510
/* }}} */
511
512
/* {{{ Execute command via shell and return complete output as string */
513
PHP_FUNCTION(shell_exec)
514
0
{
515
0
  FILE *in;
516
0
  char *command;
517
0
  size_t command_len;
518
0
  zend_string *ret;
519
0
  php_stream *stream;
520
521
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
522
0
    Z_PARAM_STRING(command, command_len)
523
0
  ZEND_PARSE_PARAMETERS_END();
524
525
0
  if (!command_len) {
526
0
    php_error_docref(NULL, E_WARNING, "Cannot execute a blank command");
527
0
    RETURN_FALSE;
528
0
  }
529
0
  if (strlen(command) != command_len) {
530
0
    php_error_docref(NULL, E_WARNING, "NULL byte detected. Possible attack");
531
0
    RETURN_FALSE;
532
0
  }
533
534
#ifdef PHP_WIN32
535
  if ((in=VCWD_POPEN(command, "rt"))==NULL) {
536
#else
537
0
  if ((in=VCWD_POPEN(command, "r"))==NULL) {
538
0
#endif
539
0
    php_error_docref(NULL, E_WARNING, "Unable to execute '%s'", command);
540
0
    RETURN_FALSE;
541
0
  }
542
543
0
  stream = php_stream_fopen_from_pipe(in, "rb");
544
0
  ret = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0);
545
0
  php_stream_close(stream);
546
547
0
  if (ret && ZSTR_LEN(ret) > 0) {
548
0
    RETVAL_STR(ret);
549
0
  }
550
0
}
551
/* }}} */
552
553
#ifdef HAVE_NICE
554
/* {{{ Change the priority of the current process */
555
PHP_FUNCTION(proc_nice)
556
0
{
557
0
  zend_long pri;
558
559
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
560
0
    Z_PARAM_LONG(pri)
561
0
  ZEND_PARSE_PARAMETERS_END();
562
563
0
  errno = 0;
564
0
  php_ignore_value(nice(pri));
565
0
  if (errno) {
566
#ifdef PHP_WIN32
567
    char *err = php_win_err();
568
    php_error_docref(NULL, E_WARNING, "%s", err);
569
    php_win_err_free(err);
570
#else
571
0
    php_error_docref(NULL, E_WARNING, "Only a super user may attempt to increase the priority of a process");
572
0
#endif
573
0
    RETURN_FALSE;
574
0
  }
575
576
0
  RETURN_TRUE;
577
0
}
578
/* }}} */
579
#endif