xref: /PHP-7.2/ext/session/mod_mm.c (revision 7a7ec01a)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-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    | Author: Sascha Schumann <sascha@schumann.cx>                         |
16    +----------------------------------------------------------------------+
17  */
18 
19 /* $Id$ */
20 
21 #include "php.h"
22 
23 #ifdef HAVE_LIBMM
24 
25 #include <unistd.h>
26 #include <mm.h>
27 #include <time.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #include <fcntl.h>
31 
32 #include "php_stdint.h"
33 #include "php_session.h"
34 #include "mod_mm.h"
35 #include "SAPI.h"
36 
37 #ifdef ZTS
38 # error mm is not thread-safe
39 #endif
40 
41 #define PS_MM_FILE "session_mm_"
42 
43 /* This list holds all data associated with one session. */
44 
45 typedef struct ps_sd {
46 	struct ps_sd *next;
47 	uint32_t hv;		/* hash value of key */
48 	time_t ctime;		/* time of last change */
49 	void *data;
50 	size_t datalen;		/* amount of valid data */
51 	size_t alloclen;	/* amount of allocated memory for data */
52 	char key[1];		/* inline key */
53 } ps_sd;
54 
55 typedef struct {
56 	MM *mm;
57 	ps_sd **hash;
58 	uint32_t hash_max;
59 	uint32_t hash_cnt;
60 	pid_t owner;
61 } ps_mm;
62 
63 static ps_mm *ps_mm_instance = NULL;
64 
65 #if 0
66 # define ps_mm_debug(a) printf a
67 #else
68 # define ps_mm_debug(a)
69 #endif
70 
ps_sd_hash(const char * data,int len)71 static inline uint32_t ps_sd_hash(const char *data, int len)
72 {
73 	uint32_t h;
74 	const char *e = data + len;
75 
76 	for (h = 2166136261U; data < e; ) {
77 		h *= 16777619;
78 		h ^= *data++;
79 	}
80 
81 	return h;
82 }
83 
hash_split(ps_mm * data)84 static void hash_split(ps_mm *data)
85 {
86 	uint32_t nmax;
87 	ps_sd **nhash;
88 	ps_sd **ohash, **ehash;
89 	ps_sd *ps, *next;
90 
91 	nmax = ((data->hash_max + 1) << 1) - 1;
92 	nhash = mm_calloc(data->mm, nmax + 1, sizeof(*data->hash));
93 
94 	if (!nhash) {
95 		/* no further memory to expand hash table */
96 		return;
97 	}
98 
99 	ehash = data->hash + data->hash_max + 1;
100 	for (ohash = data->hash; ohash < ehash; ohash++) {
101 		for (ps = *ohash; ps; ps = next) {
102 			next = ps->next;
103 			ps->next = nhash[ps->hv & nmax];
104 			nhash[ps->hv & nmax] = ps;
105 		}
106 	}
107 	mm_free(data->mm, data->hash);
108 
109 	data->hash = nhash;
110 	data->hash_max = nmax;
111 }
112 
ps_sd_new(ps_mm * data,const char * key)113 static ps_sd *ps_sd_new(ps_mm *data, const char *key)
114 {
115 	uint32_t hv, slot;
116 	ps_sd *sd;
117 	int keylen;
118 
119 	keylen = strlen(key);
120 
121 	sd = mm_malloc(data->mm, sizeof(ps_sd) + keylen);
122 	if (!sd) {
123 
124 		php_error_docref(NULL, E_WARNING, "mm_malloc failed, avail %ld, err %s", mm_available(data->mm), mm_error());
125 		return NULL;
126 	}
127 
128 	hv = ps_sd_hash(key, keylen);
129 	slot = hv & data->hash_max;
130 
131 	sd->ctime = 0;
132 	sd->hv = hv;
133 	sd->data = NULL;
134 	sd->alloclen = sd->datalen = 0;
135 
136 	memcpy(sd->key, key, keylen + 1);
137 
138 	sd->next = data->hash[slot];
139 	data->hash[slot] = sd;
140 
141 	data->hash_cnt++;
142 
143 	if (!sd->next) {
144 		if (data->hash_cnt >= data->hash_max) {
145 			hash_split(data);
146 		}
147 	}
148 
149 	ps_mm_debug(("inserting %s(%p) into slot %d\n", key, sd, slot));
150 
151 	return sd;
152 }
153 
ps_sd_destroy(ps_mm * data,ps_sd * sd)154 static void ps_sd_destroy(ps_mm *data, ps_sd *sd)
155 {
156 	uint32_t slot;
157 
158 	slot = ps_sd_hash(sd->key, strlen(sd->key)) & data->hash_max;
159 
160 	if (data->hash[slot] == sd) {
161 		data->hash[slot] = sd->next;
162 	} else {
163 		ps_sd *prev;
164 
165 		/* There must be some entry before the one we want to delete */
166 		for (prev = data->hash[slot]; prev->next != sd; prev = prev->next);
167 		prev->next = sd->next;
168 	}
169 
170 	data->hash_cnt--;
171 
172 	if (sd->data) {
173 		mm_free(data->mm, sd->data);
174 	}
175 
176 	mm_free(data->mm, sd);
177 }
178 
ps_sd_lookup(ps_mm * data,const char * key,int rw)179 static ps_sd *ps_sd_lookup(ps_mm *data, const char *key, int rw)
180 {
181 	uint32_t hv, slot;
182 	ps_sd *ret, *prev;
183 
184 	hv = ps_sd_hash(key, strlen(key));
185 	slot = hv & data->hash_max;
186 
187 	for (prev = NULL, ret = data->hash[slot]; ret; prev = ret, ret = ret->next) {
188 		if (ret->hv == hv && !strcmp(ret->key, key)) {
189 			break;
190 		}
191 	}
192 
193 	if (ret && rw && ret != data->hash[slot]) {
194 		/* Move the entry to the top of the linked list */
195 		if (prev) {
196 			prev->next = ret->next;
197 		}
198 
199 		ret->next = data->hash[slot];
200 		data->hash[slot] = ret;
201 	}
202 
203 	ps_mm_debug(("lookup(%s): ret=%p,hv=%u,slot=%d\n", key, ret, hv, slot));
204 
205 	return ret;
206 }
207 
ps_mm_key_exists(ps_mm * data,const char * key)208 static int ps_mm_key_exists(ps_mm *data, const char *key)
209 {
210 	ps_sd *sd;
211 
212 	if (!key) {
213 		return FAILURE;
214 	}
215 	sd = ps_sd_lookup(data, key, 0);
216 	if (sd) {
217 		return SUCCESS;
218 	}
219 	return FAILURE;
220 }
221 
222 ps_module ps_mod_mm = {
223 	PS_MOD_SID(mm)
224 };
225 
226 #define PS_MM_DATA ps_mm *data = PS_GET_MOD_DATA()
227 
ps_mm_initialize(ps_mm * data,const char * path)228 static int ps_mm_initialize(ps_mm *data, const char *path)
229 {
230 	data->owner = getpid();
231 	data->mm = mm_create(0, path);
232 	if (!data->mm) {
233 		return FAILURE;
234 	}
235 
236 	data->hash_cnt = 0;
237 	data->hash_max = 511;
238 	data->hash = mm_calloc(data->mm, data->hash_max + 1, sizeof(ps_sd *));
239 	if (!data->hash) {
240 		mm_destroy(data->mm);
241 		return FAILURE;
242 	}
243 
244 	return SUCCESS;
245 }
246 
ps_mm_destroy(ps_mm * data)247 static void ps_mm_destroy(ps_mm *data)
248 {
249 	int h;
250 	ps_sd *sd, *next;
251 
252 	/* This function is called during each module shutdown,
253 	   but we must not release the shared memory pool, when
254 	   an Apache child dies! */
255 	if (data->owner != getpid()) {
256 		return;
257 	}
258 
259 	for (h = 0; h < data->hash_max + 1; h++) {
260 		for (sd = data->hash[h]; sd; sd = next) {
261 			next = sd->next;
262 			ps_sd_destroy(data, sd);
263 		}
264 	}
265 
266 	mm_free(data->mm, data->hash);
267 	mm_destroy(data->mm);
268 	free(data);
269 }
270 
PHP_MINIT_FUNCTION(ps_mm)271 PHP_MINIT_FUNCTION(ps_mm)
272 {
273 	int save_path_len = strlen(PS(save_path));
274 	int mod_name_len = strlen(sapi_module.name);
275 	int euid_len;
276 	char *ps_mm_path, euid[30];
277 	int ret;
278 
279 	ps_mm_instance = calloc(sizeof(*ps_mm_instance), 1);
280 	if (!ps_mm_instance) {
281 		return FAILURE;
282 	}
283 
284 	if (!(euid_len = slprintf(euid, sizeof(euid), "%d", geteuid()))) {
285 		free(ps_mm_instance);
286 		ps_mm_instance = NULL;
287 		return FAILURE;
288 	}
289 
290 	/* Directory + '/' + File + Module Name + Effective UID + \0 */
291 	ps_mm_path = emalloc(save_path_len + 1 + (sizeof(PS_MM_FILE) - 1) + mod_name_len + euid_len + 1);
292 
293 	memcpy(ps_mm_path, PS(save_path), save_path_len);
294 	if (save_path_len && PS(save_path)[save_path_len - 1] != DEFAULT_SLASH) {
295 		ps_mm_path[save_path_len] = DEFAULT_SLASH;
296 		save_path_len++;
297 	}
298 	memcpy(ps_mm_path + save_path_len, PS_MM_FILE, sizeof(PS_MM_FILE) - 1);
299 	save_path_len += sizeof(PS_MM_FILE) - 1;
300 	memcpy(ps_mm_path + save_path_len, sapi_module.name, mod_name_len);
301 	save_path_len += mod_name_len;
302 	memcpy(ps_mm_path + save_path_len, euid, euid_len);
303 	ps_mm_path[save_path_len + euid_len] = '\0';
304 
305 	ret = ps_mm_initialize(ps_mm_instance, ps_mm_path);
306 
307 	efree(ps_mm_path);
308 
309 	if (ret != SUCCESS) {
310 		free(ps_mm_instance);
311 		ps_mm_instance = NULL;
312 		return FAILURE;
313 	}
314 
315 	php_session_register_module(&ps_mod_mm);
316 	return SUCCESS;
317 }
318 
PHP_MSHUTDOWN_FUNCTION(ps_mm)319 PHP_MSHUTDOWN_FUNCTION(ps_mm)
320 {
321 	if (ps_mm_instance) {
322 		ps_mm_destroy(ps_mm_instance);
323 		return SUCCESS;
324 	}
325 	return FAILURE;
326 }
327 
PS_OPEN_FUNC(mm)328 PS_OPEN_FUNC(mm)
329 {
330 	ps_mm_debug(("open: ps_mm_instance=%p\n", ps_mm_instance));
331 
332 	if (!ps_mm_instance) {
333 		return FAILURE;
334 	}
335 	PS_SET_MOD_DATA(ps_mm_instance);
336 
337 	return SUCCESS;
338 }
339 
PS_CLOSE_FUNC(mm)340 PS_CLOSE_FUNC(mm)
341 {
342 	PS_SET_MOD_DATA(NULL);
343 
344 	return SUCCESS;
345 }
346 
PS_READ_FUNC(mm)347 PS_READ_FUNC(mm)
348 {
349 	PS_MM_DATA;
350 	ps_sd *sd;
351 	int ret = FAILURE;
352 
353 	mm_lock(data->mm, MM_LOCK_RD);
354 
355 	/* If there is an ID and strict mode, verify existence */
356 	if (PS(use_strict_mode)
357 		&& ps_mm_key_exists(data, key->val) == FAILURE) {
358 		/* key points to PS(id), but cannot change here. */
359 		if (key) {
360 			efree(PS(id));
361 			PS(id) = NULL;
362 		}
363 		PS(id) = PS(mod)->s_create_sid((void **)&data);
364 		if (!PS(id)) {
365 			return FAILURE;
366 		}
367 		if (PS(use_cookies)) {
368 			PS(send_cookie) = 1;
369 		}
370 		php_session_reset_id();
371 		PS(session_status) = php_session_active;
372 	}
373 
374 	sd = ps_sd_lookup(data, PS(id)->val, 0);
375 	if (sd) {
376 		*val = zend_string_init(sd->data, sd->datalen, 0);
377 		ret = SUCCESS;
378 	}
379 
380 	mm_unlock(data->mm);
381 
382 	return ret;
383 }
384 
PS_WRITE_FUNC(mm)385 PS_WRITE_FUNC(mm)
386 {
387 	PS_MM_DATA;
388 	ps_sd *sd;
389 
390 	mm_lock(data->mm, MM_LOCK_RW);
391 
392 	sd = ps_sd_lookup(data, key->val, 1);
393 	if (!sd) {
394 		sd = ps_sd_new(data, key->val);
395 		ps_mm_debug(("new entry for %s\n", key->val));
396 	}
397 
398 	if (sd) {
399 		if (val->len >= sd->alloclen) {
400 			if (data->mm) {
401 				mm_free(data->mm, sd->data);
402 			}
403 			sd->alloclen = val->len + 1;
404 			sd->data = mm_malloc(data->mm, sd->alloclen);
405 
406 			if (!sd->data) {
407 				ps_sd_destroy(data, sd);
408 				php_error_docref(NULL, E_WARNING, "cannot allocate new data segment");
409 				sd = NULL;
410 			}
411 		}
412 		if (sd) {
413 			sd->datalen = val->len;
414 			memcpy(sd->data, val->val, val->len);
415 			time(&sd->ctime);
416 		}
417 	}
418 
419 	mm_unlock(data->mm);
420 
421 	return sd ? SUCCESS : FAILURE;
422 }
423 
PS_DESTROY_FUNC(mm)424 PS_DESTROY_FUNC(mm)
425 {
426 	PS_MM_DATA;
427 	ps_sd *sd;
428 
429 	mm_lock(data->mm, MM_LOCK_RW);
430 
431 	sd = ps_sd_lookup(data, key->val, 0);
432 	if (sd) {
433 		ps_sd_destroy(data, sd);
434 	}
435 
436 	mm_unlock(data->mm);
437 
438 	return SUCCESS;
439 }
440 
PS_GC_FUNC(mm)441 PS_GC_FUNC(mm)
442 {
443 	PS_MM_DATA;
444 	time_t limit;
445 	ps_sd **ohash, **ehash;
446 	ps_sd *sd, *next;
447 
448 	*nrdels = 0;
449 	ps_mm_debug(("gc\n"));
450 
451 	time(&limit);
452 
453 	limit -= maxlifetime;
454 
455 	mm_lock(data->mm, MM_LOCK_RW);
456 
457 	ehash = data->hash + data->hash_max + 1;
458 	for (ohash = data->hash; ohash < ehash; ohash++) {
459 		for (sd = *ohash; sd; sd = next) {
460 			next = sd->next;
461 			if (sd->ctime < limit) {
462 				ps_mm_debug(("purging %s\n", sd->key));
463 				ps_sd_destroy(data, sd);
464 				(*nrdels)++;
465 			}
466 		}
467 	}
468 
469 	mm_unlock(data->mm);
470 
471 	return nrdels;
472 }
473 
PS_CREATE_SID_FUNC(mm)474 PS_CREATE_SID_FUNC(mm)
475 {
476 	zend_string *sid;
477 	int maxfail = 3;
478 	PS_MM_DATA;
479 
480 	do {
481 		sid = php_session_create_id((void **)&data);
482 		/* Check collision */
483 		if (ps_mm_key_exists(data, sid->val) == SUCCESS) {
484 			if (sid) {
485 				zend_string_release(sid);
486 				sid = NULL;
487 			}
488 			if (!(maxfail--)) {
489 				return NULL;
490 			}
491 		}
492 	} while(!sid);
493 
494 	return sid;
495 }
496 
497 #endif
498 
499 /*
500  * Local variables:
501  * tab-width: 4
502  * c-basic-offset: 4
503  * End:
504  * vim600: sw=4 ts=4 fdm=marker
505  * vim<600: sw=4 ts=4
506  */
507