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