1 /*
2 +----------------------------------------------------------------------+
3 | Zend OPcache |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1998-2018 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Andi Gutmans <andi@php.net> |
16 | Zeev Suraski <zeev@php.net> |
17 | Stanislav Malyshev <stas@zend.com> |
18 | Dmitry Stogov <dmitry@php.net> |
19 +----------------------------------------------------------------------+
20 */
21
22 #include "main/php.h"
23 #include "main/fopen_wrappers.h"
24 #include "ZendAccelerator.h"
25 #include "zend_accelerator_blacklist.h"
26
27 #ifdef ZEND_WIN32
28 # define REGEX_MODE (REG_EXTENDED|REG_NOSUB|REG_ICASE)
29 #else
30 # define REGEX_MODE (REG_EXTENDED|REG_NOSUB)
31 #endif
32
33 #ifdef HAVE_GLOB
34 #ifdef PHP_WIN32
35 #include "win32/glob.h"
36 #else
37 #include <glob.h>
38 #endif
39 #endif
40
41 #include "ext/pcre/php_pcre.h"
42
43 #define ZEND_BLACKLIST_BLOCK_SIZE 32
44
45 struct _zend_regexp_list {
46 pcre2_code *re;
47 zend_regexp_list *next;
48 };
49
50 zend_blacklist accel_blacklist;
51
zend_accel_blacklist_init(zend_blacklist * blacklist)52 void zend_accel_blacklist_init(zend_blacklist *blacklist)
53 {
54 blacklist->pos = 0;
55 blacklist->size = ZEND_BLACKLIST_BLOCK_SIZE;
56
57 if (blacklist->entries != NULL) {
58 zend_accel_blacklist_shutdown(blacklist);
59 }
60
61 blacklist->entries = (zend_blacklist_entry *) calloc(sizeof(zend_blacklist_entry), blacklist->size);
62 if (!blacklist->entries) {
63 zend_accel_error(ACCEL_LOG_FATAL, "Blacklist initialization: no memory\n");
64 return;
65 }
66 blacklist->regexp_list = NULL;
67 }
68
blacklist_report_regexp_error(const char * pcre_error,int pcre_error_offset)69 static void blacklist_report_regexp_error(const char *pcre_error, int pcre_error_offset)
70 {
71 zend_accel_error(ACCEL_LOG_ERROR, "Blacklist compilation failed (offset: %d), %s\n", pcre_error_offset, pcre_error);
72 }
73
zend_accel_blacklist_update_regexp(zend_blacklist * blacklist)74 static void zend_accel_blacklist_update_regexp(zend_blacklist *blacklist)
75 {
76 PCRE2_UCHAR pcre_error[128];
77 int i, errnumber;
78 PCRE2_SIZE pcre_error_offset;
79 zend_regexp_list **regexp_list_it, *it;
80 char regexp[12*1024], *p, *end, *c, *backtrack = NULL;
81 pcre2_compile_context *cctx = php_pcre_cctx();
82
83 if (blacklist->pos == 0) {
84 /* we have no blacklist to talk about */
85 return;
86 }
87
88 regexp_list_it = &(blacklist->regexp_list);
89
90 regexp[0] = '^';
91 regexp[1] = '(';
92 p = regexp + 2;
93 end = regexp + sizeof(regexp) - sizeof("[^\\\\]*)\0");
94
95 for (i = 0; i < blacklist->pos; ) {
96 c = blacklist->entries[i].path;
97 if (p + blacklist->entries[i].path_length < end) {
98 while (*c && p < end) {
99 switch (*c) {
100 case '?':
101 c++;
102 #ifdef ZEND_WIN32
103 p[0] = '['; /* * => [^\\] on Win32 */
104 p[1] = '^';
105 p[2] = '\\';
106 p[3] = '\\';
107 p[4] = ']';
108 p += 5;
109 #else
110 p[0] = '['; /* * => [^/] on *nix */
111 p[1] = '^';
112 p[2] = '/';
113 p[3] = ']';
114 p += 4;
115 #endif
116 break;
117 case '*':
118 c++;
119 if (*c == '*') {
120 c++;
121 p[0] = '.'; /* ** => .* */
122 p[1] = '*';
123 p += 2;
124 } else {
125 #ifdef ZEND_WIN32
126 p[0] = '['; /* * => [^\\]* on Win32 */
127 p[1] = '^';
128 p[2] = '\\';
129 p[3] = '\\';
130 p[4] = ']';
131 p[5] = '*';
132 p += 6;
133 #else
134 p[0] = '['; /* * => [^/]* on *nix */
135 p[1] = '^';
136 p[2] = '/';
137 p[3] = ']';
138 p[4] = '*';
139 p += 5;
140 #endif
141 }
142 break;
143 case '^':
144 case '.':
145 case '[':
146 case ']':
147 case '$':
148 case '(':
149 case ')':
150 case '|':
151 case '+':
152 case '{':
153 case '}':
154 case '\\':
155 *p++ = '\\';
156 /* break missing intentionally */
157 default:
158 *p++ = *c++;
159 }
160 }
161 }
162
163 if (*c || i == blacklist->pos - 1) {
164 if (*c) {
165 if (!backtrack) {
166 zend_accel_error(ACCEL_LOG_ERROR, "Too long blacklist entry\n");
167 }
168 p = backtrack;
169 } else {
170 i++;
171 }
172 *p++ = ')';
173
174 it = (zend_regexp_list*)malloc(sizeof(zend_regexp_list));
175 if (!it) {
176 zend_accel_error(ACCEL_LOG_ERROR, "malloc() failed\n");
177 return;
178 }
179 it->next = NULL;
180
181 if ((it->re = pcre2_compile((PCRE2_SPTR)regexp, p - regexp, PCRE2_NO_AUTO_CAPTURE, &errnumber, &pcre_error_offset, cctx)) == NULL) {
182 free(it);
183 pcre2_get_error_message(errnumber, pcre_error, sizeof(pcre_error));
184 blacklist_report_regexp_error((char *)pcre_error, pcre_error_offset);
185 return;
186 }
187 #ifdef HAVE_PCRE_JIT_SUPPORT
188 if (0 > pcre2_jit_compile(it->re, PCRE2_JIT_COMPLETE)) {
189 /* Don't return here, even JIT could fail to compile, the pattern is still usable. */
190 pcre2_get_error_message(errnumber, pcre_error, sizeof(pcre_error));
191 zend_accel_error(ACCEL_LOG_WARNING, "Blacklist JIT compilation failed, %s\n", pcre_error);
192 }
193 #endif
194 /* prepare for the next iteration */
195 p = regexp + 2;
196 *regexp_list_it = it;
197 regexp_list_it = &it->next;
198 } else {
199 backtrack = p;
200 *p++ = '|';
201 i++;
202 }
203 }
204 }
205
zend_accel_blacklist_shutdown(zend_blacklist * blacklist)206 void zend_accel_blacklist_shutdown(zend_blacklist *blacklist)
207 {
208 zend_blacklist_entry *p = blacklist->entries, *end = blacklist->entries + blacklist->pos;
209
210 while (p<end) {
211 free(p->path);
212 p++;
213 }
214 free(blacklist->entries);
215 blacklist->entries = NULL;
216 if (blacklist->regexp_list) {
217 zend_regexp_list *temp, *it = blacklist->regexp_list;
218 while (it) {
219 pcre2_code_free(it->re);
220 temp = it;
221 it = it->next;
222 free(temp);
223 }
224 }
225 }
226
zend_accel_blacklist_allocate(zend_blacklist * blacklist)227 static inline void zend_accel_blacklist_allocate(zend_blacklist *blacklist)
228 {
229 if (blacklist->pos == blacklist->size) {
230 blacklist->size += ZEND_BLACKLIST_BLOCK_SIZE;
231 blacklist->entries = (zend_blacklist_entry *) realloc(blacklist->entries, sizeof(zend_blacklist_entry)*blacklist->size);
232 }
233 }
234
235 #ifdef HAVE_GLOB
zend_accel_blacklist_loadone(zend_blacklist * blacklist,char * filename)236 static void zend_accel_blacklist_loadone(zend_blacklist *blacklist, char *filename)
237 #else
238 void zend_accel_blacklist_load(zend_blacklist *blacklist, char *filename)
239 #endif
240 {
241 char buf[MAXPATHLEN + 1], real_path[MAXPATHLEN + 1], *blacklist_path = NULL;
242 FILE *fp;
243 int path_length, blacklist_path_length;
244
245 if ((fp = fopen(filename, "r")) == NULL) {
246 zend_accel_error(ACCEL_LOG_WARNING, "Cannot load blacklist file: %s\n", filename);
247 return;
248 }
249
250 zend_accel_error(ACCEL_LOG_DEBUG,"Loading blacklist file: '%s'", filename);
251
252 if (VCWD_REALPATH(filename, buf)) {
253 blacklist_path_length = zend_dirname(buf, strlen(buf));
254 blacklist_path = zend_strndup(buf, blacklist_path_length);
255 }
256
257 memset(buf, 0, sizeof(buf));
258 memset(real_path, 0, sizeof(real_path));
259
260 while (fgets(buf, MAXPATHLEN, fp) != NULL) {
261 char *path_dup, *pbuf;
262 path_length = strlen(buf);
263 if (path_length > 0 && buf[path_length - 1] == '\n') {
264 buf[--path_length] = 0;
265 if (path_length > 0 && buf[path_length - 1] == '\r') {
266 buf[--path_length] = 0;
267 }
268 }
269
270 /* Strip ctrl-m prefix */
271 pbuf = &buf[0];
272 while (*pbuf == '\r') {
273 *pbuf++ = 0;
274 path_length--;
275 }
276
277 /* strip \" */
278 if (pbuf[0] == '\"' && pbuf[path_length - 1]== '\"') {
279 *pbuf++ = 0;
280 path_length -= 2;
281 }
282
283 if (path_length == 0) {
284 continue;
285 }
286
287 /* skip comments */
288 if (pbuf[0]==';') {
289 continue;
290 }
291
292 path_dup = zend_strndup(pbuf, path_length);
293 if (blacklist_path) {
294 expand_filepath_ex(path_dup, real_path, blacklist_path, blacklist_path_length);
295 } else {
296 expand_filepath(path_dup, real_path);
297 }
298 path_length = strlen(real_path);
299
300 free(path_dup);
301
302 zend_accel_blacklist_allocate(blacklist);
303 blacklist->entries[blacklist->pos].path_length = path_length;
304 blacklist->entries[blacklist->pos].path = (char *)malloc(path_length + 1);
305 if (!blacklist->entries[blacklist->pos].path) {
306 zend_accel_error(ACCEL_LOG_ERROR, "malloc() failed\n");
307 fclose(fp);
308 return;
309 }
310 blacklist->entries[blacklist->pos].id = blacklist->pos;
311 memcpy(blacklist->entries[blacklist->pos].path, real_path, path_length + 1);
312 blacklist->pos++;
313 }
314 fclose(fp);
315 if (blacklist_path) {
316 free(blacklist_path);
317 }
318 zend_accel_blacklist_update_regexp(blacklist);
319 }
320
321 #ifdef HAVE_GLOB
zend_accel_blacklist_load(zend_blacklist * blacklist,char * filename)322 void zend_accel_blacklist_load(zend_blacklist *blacklist, char *filename)
323 {
324 glob_t globbuf;
325 int ret;
326 unsigned int i;
327
328 memset(&globbuf, 0, sizeof(glob_t));
329
330 ret = glob(filename, 0, NULL, &globbuf);
331 #ifdef GLOB_NOMATCH
332 if (ret == GLOB_NOMATCH || !globbuf.gl_pathc) {
333 #else
334 if (!globbuf.gl_pathc) {
335 #endif
336 zend_accel_error(ACCEL_LOG_WARNING, "No blacklist file found matching: %s\n", filename);
337 } else {
338 for(i=0 ; i<globbuf.gl_pathc; i++) {
339 zend_accel_blacklist_loadone(blacklist, globbuf.gl_pathv[i]);
340 }
341 globfree(&globbuf);
342 }
343 }
344 #endif
345
346 zend_bool zend_accel_blacklist_is_blacklisted(zend_blacklist *blacklist, char *verify_path, size_t verify_path_len)
347 {
348 int ret = 0;
349 zend_regexp_list *regexp_list_it = blacklist->regexp_list;
350 pcre2_match_context *mctx = php_pcre_mctx();
351
352 if (regexp_list_it == NULL) {
353 return 0;
354 }
355 while (regexp_list_it != NULL) {
356 pcre2_match_data *match_data = php_pcre_create_match_data(0, regexp_list_it->re);
357 if (!match_data) {
358 /* Alloc failed, but next one could still come through and match. */
359 continue;
360 }
361 int rc = pcre2_match(regexp_list_it->re, (PCRE2_SPTR)verify_path, verify_path_len, 0, 0, match_data, mctx);
362 if (rc >= 0) {
363 ret = 1;
364 php_pcre_free_match_data(match_data);
365 break;
366 }
367 php_pcre_free_match_data(match_data);
368 regexp_list_it = regexp_list_it->next;
369 }
370 return ret;
371 }
372
373 void zend_accel_blacklist_apply(zend_blacklist *blacklist, blacklist_apply_func_arg_t func, void *argument)
374 {
375 int i;
376
377 for (i = 0; i < blacklist->pos; i++) {
378 func(&blacklist->entries[i], argument);
379 }
380 }
381