1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
|
Backported from 5.5 for 5.4 by Remi Collet
From f4d7bbf4ace4ea21c8f95c2d1177bd56a21b86b9 Mon Sep 17 00:00:00 2001
From: Anatol Belski <ab@php.net>
Date: Thu, 28 Jan 2016 13:45:43 +0100
Subject: [PATCH] backport the escapeshell* functions hardening branch
---
ext/standard/basic_functions.c | 1 +
ext/standard/exec.c | 76 +++++++++++++++++++++++++++++++++++++++---
ext/standard/exec.h | 1 +
3 files changed, 73 insertions(+), 5 deletions(-)
diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
index f8cb91e..50b6bc7 100644
--- a/ext/standard/basic_functions.c
+++ b/ext/standard/basic_functions.c
@@ -3613,6 +3613,7 @@
#ifdef PHP_CAN_SUPPORT_PROC_OPEN
PHP_MINIT(proc_open)(INIT_FUNC_ARGS_PASSTHRU);
#endif
+ PHP_MINIT(exec)(INIT_FUNC_ARGS_PASSTHRU);
PHP_MINIT(user_streams)(INIT_FUNC_ARGS_PASSTHRU);
PHP_MINIT(imagetypes)(INIT_FUNC_ARGS_PASSTHRU);
diff --git a/ext/standard/exec.c b/ext/standard/exec.c
index 66d4537..461bef4 100644
--- a/ext/standard/exec.c
+++ b/ext/standard/exec.c
@@ -46,10 +46,42 @@
#include <fcntl.h>
#endif
-#if HAVE_NICE && HAVE_UNISTD_H
+#if HAVE_UNISTD_H
#include <unistd.h>
#endif
+#ifdef PHP_WIN32
+# include "win32/php_stdint.h"
+#else
+# if HAVE_INTTYPES_H
+# include <inttypes.h>
+# elif HAVE_STDINT_H
+# include <stdint.h>
+# endif
+#endif
+
+static int cmd_max_len;
+
+/* {{{ PHP_MINIT_FUNCTION(exec) */
+PHP_MINIT_FUNCTION(exec)
+{
+#ifdef _SC_ARG_MAX
+ cmd_max_len = sysconf(_SC_ARG_MAX);
+#elif defined(ARG_MAX)
+ cmd_max_len = ARG_MAX;
+#elif defined(PHP_WIN32)
+ /* Executed commands will run through cmd.exe. As long as it's the case,
+ it's just the constant limit.*/
+ cmd_max_len = 8192;
+#else
+ /* This is just an arbitrary value for the fallback case. */
+ cmd_max_len = 4096;
+#endif
+
+ return SUCCESS;
+}
+/* }}} */
+
/* {{{ php_exec
* If type==0, only last line of output is returned (exec)
* If type==1, all lines will be printed and last lined returned (system)
@@ -244,13 +276,20 @@ PHP_FUNCTION(passthru)
*/
PHPAPI char *php_escape_shell_cmd(char *str)
{
- register int x, y, l = strlen(str);
+ register int x, y;
+ size_t l = strlen(str);
+ uint64_t estimate = (2 * (uint64_t)l) + 1;
char *cmd;
char *p = NULL;
- size_t estimate = (2 * l) + 1;
TSRMLS_FETCH();
+ /* max command line length - two single quotes - \0 byte length */
+ if (l > cmd_max_len - 2 - 1) {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "Command exceeds the allowed length of %d bytes", cmd_max_len);
+ return NULL;
+ }
+
cmd = safe_emalloc(2, l, 1);
for (x = 0, y = 0; x < l; x++) {
@@ -322,6 +361,12 @@ PHPAPI char *php_escape_shell_cmd(char *str)
}
cmd[y] = '\0';
+ if (y - 1 > cmd_max_len) {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "Escaped command exceeds the allowed length of %d bytes", cmd_max_len);
+ efree(cmd);
+ return NULL;
+ }
+
if ((estimate - y) > 4096) {
/* realloc if the estimate was way overill
* Arbitrary cutoff point of 4096 */
@@ -336,12 +381,19 @@ PHPAPI char *php_escape_shell_cmd(char *str)
*/
PHPAPI char *php_escape_shell_arg(char *str)
{
- int x, y = 0, l = strlen(str);
+ int x, y = 0;
+ size_t l = strlen(str);
char *cmd;
- size_t estimate = (4 * l) + 3;
+ uint64_t estimate = (4 * (uint64_t)l) + 3;
TSRMLS_FETCH();
+ /* max command line length - two single quotes - \0 byte length */
+ if (l > cmd_max_len - 2 - 1) {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "Argument exceeds the allowed length of %d bytes", cmd_max_len);
+ return NULL;
+ }
+
cmd = safe_emalloc(4, l, 3); /* worst case */
#ifdef PHP_WIN32
@@ -396,6 +448,12 @@ PHPAPI char *php_escape_shell_arg(char *str)
#endif
cmd[y] = '\0';
+ if (y - 1 > cmd_max_len) {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "Escaped argument exceeds the allowed length of %d bytes", cmd_max_len);
+ efree(cmd);
+ return NULL;
+ }
+
if ((estimate - y) > 4096) {
/* realloc if the estimate was way overill
* Arbitrary cutoff point of 4096 */
@@ -418,6 +476,10 @@ PHP_FUNCTION(escapeshellcmd)
}
if (command_len) {
+ if (command_len != strlen(command)) {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "Input string contains NULL bytes");
+ return;
+ }
cmd = php_escape_shell_cmd(command);
RETVAL_STRING(cmd, 0);
} else {
@@ -439,6 +501,10 @@ PHP_FUNCTION(escapeshellarg)
}
if (argument) {
+ if (argument_len != strlen(argument)) {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "Input string contains NULL bytes");
+ return;
+ }
cmd = php_escape_shell_arg(argument);
RETVAL_STRING(cmd, 0);
}
diff --git a/ext/standard/exec.h b/ext/standard/exec.h
index 399325c..b106838 100644
--- a/ext/standard/exec.h
+++ b/ext/standard/exec.h
@@ -33,6 +33,7 @@ PHP_FUNCTION(proc_close);
PHP_FUNCTION(proc_terminate);
PHP_FUNCTION(proc_nice);
PHP_MINIT_FUNCTION(proc_open);
+PHP_MINIT_FUNCTION(exec);
PHPAPI char *php_escape_shell_cmd(char *);
PHPAPI char *php_escape_shell_arg(char *);
From 828364e59ca08c2ff3328a648522f9eb05ebbaa3 Mon Sep 17 00:00:00 2001
From: Anatol Belski <ab@php.net>
Date: Thu, 28 Jan 2016 13:27:26 +0100
Subject: [PATCH] add tests
---
.../tests/general_functions/escapeshellarg_bug71039.phpt | 10 ++++++++++
.../tests/general_functions/escapeshellarg_bug71270.phpt | 12 ++++++++++++
.../tests/general_functions/escapeshellcmd_bug71039.phpt | 10 ++++++++++
.../tests/general_functions/escapeshellcmd_bug71270.phpt | 12 ++++++++++++
4 files changed, 44 insertions(+)
create mode 100644 ext/standard/tests/general_functions/escapeshellarg_bug71039.phpt
create mode 100644 ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt
create mode 100644 ext/standard/tests/general_functions/escapeshellcmd_bug71039.phpt
create mode 100644 ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt
diff --git a/ext/standard/tests/general_functions/escapeshellarg_bug71039.phpt b/ext/standard/tests/general_functions/escapeshellarg_bug71039.phpt
new file mode 100644
index 0000000..cbb3f6f
--- /dev/null
+++ b/ext/standard/tests/general_functions/escapeshellarg_bug71039.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test escapeshellarg() string with \0 bytes
+--FILE--
+<?php
+escapeshellarg("hello\0world");
+
+?>
+===DONE===
+--EXPECTF--
+Fatal error: escapeshellarg(): Input string contains NULL bytes in %s on line %d
diff --git a/ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt b/ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt
new file mode 100644
index 0000000..c57da36
--- /dev/null
+++ b/ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Test escapeshellarg() allowed argument length
+--FILE--
+<?php
+ini_set('memory_limit', -1);
+$var_2 = str_repeat('A', 1024*1024*64);
+escapeshellarg($var_2);
+
+?>
+===DONE===
+--EXPECTF--
+Fatal error: escapeshellarg(): Argument exceeds the allowed length of %d bytes in %s on line %d
diff --git a/ext/standard/tests/general_functions/escapeshellcmd_bug71039.phpt b/ext/standard/tests/general_functions/escapeshellcmd_bug71039.phpt
new file mode 100644
index 0000000..0a4d7ea
--- /dev/null
+++ b/ext/standard/tests/general_functions/escapeshellcmd_bug71039.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test escapeshellcmd() string with \0 bytes
+--FILE--
+<?php
+escapeshellcmd("hello\0world");
+
+?>
+===DONE===
+--EXPECTF--
+Fatal error: escapeshellcmd(): Input string contains NULL bytes in %s on line %d
diff --git a/ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt b/ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt
new file mode 100644
index 0000000..4686193
--- /dev/null
+++ b/ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Test escapeshellcmd() allowed argument length
+--FILE--
+<?php
+ini_set('memory_limit', -1);
+$var_2 = str_repeat('A', 1024*1024*64);
+escapeshellcmd($var_2);
+
+?>
+===DONE===
+--EXPECTF--
+Fatal error: escapeshellcmd(): Command exceeds the allowed length of %d bytes in %s on line %d
From 377d353c9f8aad6f79f3cf84aad3e2f6d65fa456 Mon Sep 17 00:00:00 2001
From: Anatol Belski <ab@php.net>
Date: Tue, 2 Feb 2016 14:19:10 +0100
Subject: [PATCH] add error check to sysconf call
---
ext/standard/exec.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/ext/standard/exec.c b/ext/standard/exec.c
index 461bef4..329066e 100644
--- a/ext/standard/exec.c
+++ b/ext/standard/exec.c
@@ -50,6 +50,10 @@
#include <unistd.h>
#endif
+#if HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
#ifdef PHP_WIN32
# include "win32/php_stdint.h"
#else
@@ -67,6 +71,13 @@ PHP_MINIT_FUNCTION(exec)
{
#ifdef _SC_ARG_MAX
cmd_max_len = sysconf(_SC_ARG_MAX);
+ if (-1 == cmd_max_len) {
+#ifdef _POSIX_ARG_MAX
+ cmd_max_len = _POSIX_ARG_MAX;
+#else
+ cmd_max_len = 4096;
+#endif
+ }
#elif defined(ARG_MAX)
cmd_max_len = ARG_MAX;
#elif defined(PHP_WIN32)
|