1 /*
2    +----------------------------------------------------------------------+
3    | Zend OPcache                                                         |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 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    | https://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(blacklist->size, sizeof(zend_blacklist_entry));
62 	if (!blacklist->entries) {
63 		zend_accel_error_noreturn(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_noreturn(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 						ZEND_FALLTHROUGH;
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_noreturn(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_noreturn(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 (PCRE_G(jit)) {
189 				if (0 > pcre2_jit_compile(it->re, PCRE2_JIT_COMPLETE)) {
190 					/* Don't return here, even JIT could fail to compile, the pattern is still usable. */
191 					pcre2_get_error_message(errnumber, pcre_error, sizeof(pcre_error));
192 					zend_accel_error(ACCEL_LOG_WARNING, "Blacklist JIT compilation failed, %s\n", pcre_error);
193 				}
194 			}
195 #endif
196 			/* prepare for the next iteration */
197 			p = regexp + 2;
198 			*regexp_list_it = it;
199 			regexp_list_it = &it->next;
200 		} else {
201 			backtrack = p;
202 			*p++ = '|';
203 			i++;
204 		}
205 	}
206 }
207 
zend_accel_blacklist_shutdown(zend_blacklist * blacklist)208 void zend_accel_blacklist_shutdown(zend_blacklist *blacklist)
209 {
210 	if (!blacklist->entries) {
211 		return;
212 	}
213 
214 	zend_blacklist_entry *p = blacklist->entries, *end = blacklist->entries + blacklist->pos;
215 	while (p<end) {
216 		free(p->path);
217 		p++;
218 	}
219 	free(blacklist->entries);
220 	blacklist->entries = NULL;
221 	if (blacklist->regexp_list) {
222 		zend_regexp_list *temp, *it = blacklist->regexp_list;
223 		while (it) {
224 			pcre2_code_free(it->re);
225 			temp = it;
226 			it = it->next;
227 			free(temp);
228 		}
229 	}
230 }
231 
zend_accel_blacklist_allocate(zend_blacklist * blacklist)232 static inline void zend_accel_blacklist_allocate(zend_blacklist *blacklist)
233 {
234 	if (blacklist->pos == blacklist->size) {
235 		blacklist->size += ZEND_BLACKLIST_BLOCK_SIZE;
236 		blacklist->entries = (zend_blacklist_entry *) realloc(blacklist->entries, sizeof(zend_blacklist_entry)*blacklist->size);
237 	}
238 }
239 
zend_accel_blacklist_loadone(zend_blacklist * blacklist,char * filename)240 static void zend_accel_blacklist_loadone(zend_blacklist *blacklist, char *filename)
241 {
242 	char buf[MAXPATHLEN + 1], real_path[MAXPATHLEN + 1], *blacklist_path = NULL;
243 	FILE *fp;
244 	int path_length, blacklist_path_length = 0;
245 
246 	if ((fp = fopen(filename, "r")) == NULL) {
247 		zend_accel_error(ACCEL_LOG_WARNING, "Cannot load blacklist file: %s\n", filename);
248 		return;
249 	}
250 
251 	zend_accel_error(ACCEL_LOG_DEBUG,"Loading blacklist file:  '%s'", filename);
252 
253 	if (VCWD_REALPATH(filename, buf)) {
254 		blacklist_path_length = zend_dirname(buf, strlen(buf));
255 		blacklist_path = zend_strndup(buf, blacklist_path_length);
256 	}
257 
258 	memset(buf, 0, sizeof(buf));
259 	memset(real_path, 0, sizeof(real_path));
260 
261 	while (fgets(buf, MAXPATHLEN, fp) != NULL) {
262 		char *path_dup, *pbuf;
263 		path_length = strlen(buf);
264 		if (path_length > 0 && buf[path_length - 1] == '\n') {
265 			buf[--path_length] = 0;
266 			if (path_length > 0 && buf[path_length - 1] == '\r') {
267 				buf[--path_length] = 0;
268 			}
269 		}
270 
271 		/* Strip ctrl-m prefix */
272 		pbuf = &buf[0];
273 		while (*pbuf == '\r') {
274 			*pbuf++ = 0;
275 			path_length--;
276 		}
277 
278 		/* strip \" */
279 		if (path_length > 0 && pbuf[0] == '\"' && pbuf[path_length - 1]== '\"') {
280 			*pbuf++ = 0;
281 			path_length -= 2;
282 		}
283 
284 		if (path_length <= 0) {
285 			continue;
286 		}
287 
288 		/* skip comments */
289 		if (pbuf[0]==';') {
290 			continue;
291 		}
292 
293 		path_dup = zend_strndup(pbuf, path_length);
294 		if (blacklist_path) {
295 			expand_filepath_ex(path_dup, real_path, blacklist_path, blacklist_path_length);
296 		} else {
297 			expand_filepath(path_dup, real_path);
298 		}
299 		path_length = strlen(real_path);
300 
301 		free(path_dup);
302 
303 		zend_accel_blacklist_allocate(blacklist);
304 		blacklist->entries[blacklist->pos].path_length = path_length;
305 		blacklist->entries[blacklist->pos].path = (char *)malloc(path_length + 1);
306 		if (!blacklist->entries[blacklist->pos].path) {
307 			zend_accel_error_noreturn(ACCEL_LOG_ERROR, "malloc() failed\n");
308 			fclose(fp);
309 			return;
310 		}
311 		blacklist->entries[blacklist->pos].id = blacklist->pos;
312 		memcpy(blacklist->entries[blacklist->pos].path, real_path, path_length + 1);
313 		blacklist->pos++;
314 	}
315 	fclose(fp);
316 	if (blacklist_path) {
317 		free(blacklist_path);
318 	}
319 }
320 
zend_accel_blacklist_load(zend_blacklist * blacklist,char * filename)321 void zend_accel_blacklist_load(zend_blacklist *blacklist, char *filename)
322 {
323 #ifdef HAVE_GLOB
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 #else
344 	zend_accel_blacklist_loadone(blacklist, filename);
345 #endif
346 	zend_accel_blacklist_update_regexp(blacklist);
347 }
348 
349 bool zend_accel_blacklist_is_blacklisted(zend_blacklist *blacklist, char *verify_path, size_t verify_path_len)
350 {
351 	int ret = 0;
352 	zend_regexp_list *regexp_list_it = blacklist->regexp_list;
353 	pcre2_match_context *mctx = php_pcre_mctx();
354 
355 	if (regexp_list_it == NULL) {
356 		return 0;
357 	}
358 	while (regexp_list_it != NULL) {
359 		pcre2_match_data *match_data = php_pcre_create_match_data(0, regexp_list_it->re);
360 		if (!match_data) {
361 			/* Alloc failed, but next one could still come through and match. */
362 			continue;
363 		}
364 		int rc = pcre2_match(regexp_list_it->re, (PCRE2_SPTR)verify_path, verify_path_len, 0, 0, match_data, mctx);
365 		if (rc >= 0) {
366 			ret = 1;
367 			php_pcre_free_match_data(match_data);
368 			break;
369 		}
370 		php_pcre_free_match_data(match_data);
371 		regexp_list_it = regexp_list_it->next;
372 	}
373 	return ret;
374 }
375 
376 void zend_accel_blacklist_apply(zend_blacklist *blacklist, blacklist_apply_func_arg_t func, void *argument)
377 {
378 	int i;
379 
380 	for (i = 0; i < blacklist->pos; i++) {
381 		func(&blacklist->entries[i], argument);
382 	}
383 }
384