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