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