1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00
Files
archived-php-src/ext/session/mod_mm.c
Gina Peter Banyard 4b01cd1bf3 ext/session/mod_mm: implement VALIDATE_SID handler (#21178)
Rather than "manually" doing it in the READ handler.

At the same time, get rid of various inconsistent legacy handler macro definitions, thus mandating all modules to implement the create and validate SID handlers.

The only handler that remains optional is the update timestamp one.
2026-02-10 12:17:47 +00:00

492 lines
9.8 KiB
C

/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Sascha Schumann <sascha@schumann.cx> |
+----------------------------------------------------------------------+
*/
#include "php.h"
#ifdef HAVE_LIBMM
#include <unistd.h>
#include <mm.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdint.h>
#include "php_session.h"
#include "mod_mm.h"
#include "SAPI.h"
#ifdef ZTS
# error mm is not thread-safe
#endif
#define PS_MM_FILE "session_mm_"
/* This list holds all data associated with one session. */
typedef struct ps_sd {
struct ps_sd *next;
uint32_t hv; /* hash value of key */
time_t ctime; /* time of last change */
void *data;
size_t datalen; /* amount of valid data */
size_t alloclen; /* amount of allocated memory for data */
zend_string *key;
} ps_sd;
typedef struct {
MM *mm;
ps_sd **hash;
uint32_t hash_max;
uint32_t hash_cnt;
pid_t owner;
} ps_mm;
static ps_mm *ps_mm_instance = NULL;
#if 0
# define ps_mm_debug(a) printf a
#else
# define ps_mm_debug(a)
#endif
static inline uint32_t ps_sd_hash(const zend_string *data)
{
uint32_t h;
const char *data_char = ZSTR_VAL(data);
const char *e = ZSTR_VAL(data) + ZSTR_LEN(data);
for (h = 2166136261U; data_char < e; ) {
h *= 16777619;
h ^= *data_char++;
}
return h;
}
static void hash_split(ps_mm *data)
{
uint32_t nmax;
ps_sd **nhash;
ps_sd **ohash, **ehash;
ps_sd *ps, *next;
nmax = ((data->hash_max + 1) << 1) - 1;
nhash = mm_calloc(data->mm, nmax + 1, sizeof(*data->hash));
if (!nhash) {
/* no further memory to expand hash table */
return;
}
ehash = data->hash + data->hash_max + 1;
for (ohash = data->hash; ohash < ehash; ohash++) {
for (ps = *ohash; ps; ps = next) {
next = ps->next;
ps->next = nhash[ps->hv & nmax];
nhash[ps->hv & nmax] = ps;
}
}
mm_free(data->mm, data->hash);
data->hash = nhash;
data->hash_max = nmax;
}
static ps_sd *ps_sd_new(ps_mm *data, zend_string *key)
{
uint32_t hv, slot;
ps_sd *sd;
sd = mm_malloc(data->mm, sizeof(ps_sd) + ZSTR_LEN(key));
if (!sd) {
php_error_docref(NULL, E_WARNING, "mm_malloc failed, avail %ld, err %s", mm_available(data->mm), mm_error());
return NULL;
}
hv = ps_sd_hash(key);
slot = hv & data->hash_max;
sd->ctime = 0;
sd->hv = hv;
sd->data = NULL;
sd->alloclen = sd->datalen = 0;
sd->key = zend_string_copy(key);
sd->next = data->hash[slot];
data->hash[slot] = sd;
data->hash_cnt++;
if (!sd->next) {
if (data->hash_cnt >= data->hash_max) {
hash_split(data);
}
}
ps_mm_debug(("inserting %s(%p) into slot %d\n", ZSTR_VAL(key), sd, slot));
return sd;
}
static void ps_sd_destroy(ps_mm *data, ps_sd *sd)
{
uint32_t slot;
slot = ps_sd_hash(sd->key) & data->hash_max;
if (data->hash[slot] == sd) {
data->hash[slot] = sd->next;
} else {
ps_sd *prev;
/* There must be some entry before the one we want to delete */
for (prev = data->hash[slot]; prev->next != sd; prev = prev->next);
prev->next = sd->next;
}
data->hash_cnt--;
if (sd->data) {
mm_free(data->mm, sd->data);
}
zend_string_release(sd->key);
mm_free(data->mm, sd);
}
static ps_sd *ps_sd_lookup(ps_mm *data, const zend_string *key, bool rw)
{
uint32_t hv, slot;
ps_sd *ret, *prev;
hv = ps_sd_hash(key);
slot = hv & data->hash_max;
for (prev = NULL, ret = data->hash[slot]; ret; prev = ret, ret = ret->next) {
if (ret->hv == hv && zend_string_equals(ret->key, key)) {
break;
}
}
if (ret && rw && ret != data->hash[slot]) {
/* Move the entry to the top of the linked list */
if (prev) {
prev->next = ret->next;
}
ret->next = data->hash[slot];
data->hash[slot] = ret;
}
ps_mm_debug(("lookup(%s): ret=%p,hv=%u,slot=%d\n", ZSTR_VAL(key), ret, hv, slot));
return ret;
}
static zend_result ps_mm_key_exists(ps_mm *data, const zend_string *key)
{
ps_sd *sd;
if (!key) {
return FAILURE;
}
sd = ps_sd_lookup(data, key, false);
if (sd) {
return SUCCESS;
}
return FAILURE;
}
const ps_module ps_mod_mm = {
PS_MOD(mm)
};
#define PS_MM_DATA ps_mm *data = PS_GET_MOD_DATA()
static zend_result ps_mm_initialize(ps_mm *data, const char *path)
{
data->owner = getpid();
data->mm = mm_create(0, path);
if (!data->mm) {
return FAILURE;
}
data->hash_cnt = 0;
data->hash_max = 511;
data->hash = mm_calloc(data->mm, data->hash_max + 1, sizeof(ps_sd *));
if (!data->hash) {
mm_destroy(data->mm);
return FAILURE;
}
return SUCCESS;
}
static void ps_mm_destroy(ps_mm *data)
{
ps_sd *sd, *next;
/* This function is called during each module shutdown,
but we must not release the shared memory pool, when
an Apache child dies! */
if (data->owner != getpid()) {
return;
}
for (int h = 0; h < data->hash_max + 1; h++) {
for (sd = data->hash[h]; sd; sd = next) {
next = sd->next;
ps_sd_destroy(data, sd);
}
}
mm_free(data->mm, data->hash);
mm_destroy(data->mm);
free(data);
}
PHP_MINIT_FUNCTION(ps_mm)
{
size_t save_path_len = ZSTR_LEN(PS(save_path));
size_t mod_name_len = strlen(sapi_module.name);
size_t euid_len;
char *ps_mm_path, euid[30];
zend_result ret;
ps_mm_instance = calloc(sizeof(*ps_mm_instance), 1);
if (!ps_mm_instance) {
return FAILURE;
}
if (!(euid_len = slprintf(euid, sizeof(euid), "%d", geteuid()))) {
free(ps_mm_instance);
ps_mm_instance = NULL;
return FAILURE;
}
/* Directory + '/' + File + Module Name + Effective UID + \0 */
ps_mm_path = emalloc(save_path_len + 1 + (sizeof(PS_MM_FILE) - 1) + mod_name_len + euid_len + 1);
memcpy(ps_mm_path, ZSTR_VAL(PS(save_path)), save_path_len);
if (save_path_len && ZSTR_VAL(PS(save_path))[save_path_len - 1] != DEFAULT_SLASH) {
ps_mm_path[save_path_len] = DEFAULT_SLASH;
save_path_len++;
}
memcpy(ps_mm_path + save_path_len, PS_MM_FILE, sizeof(PS_MM_FILE) - 1);
save_path_len += sizeof(PS_MM_FILE) - 1;
memcpy(ps_mm_path + save_path_len, sapi_module.name, mod_name_len);
save_path_len += mod_name_len;
memcpy(ps_mm_path + save_path_len, euid, euid_len);
ps_mm_path[save_path_len + euid_len] = '\0';
ret = ps_mm_initialize(ps_mm_instance, ps_mm_path);
efree(ps_mm_path);
if (ret == FAILURE) {
free(ps_mm_instance);
ps_mm_instance = NULL;
return FAILURE;
}
php_session_register_module(&ps_mod_mm);
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(ps_mm)
{
if (ps_mm_instance) {
ps_mm_destroy(ps_mm_instance);
return SUCCESS;
}
return FAILURE;
}
PS_OPEN_FUNC(mm)
{
ps_mm_debug(("open: ps_mm_instance=%p\n", ps_mm_instance));
if (!ps_mm_instance) {
return FAILURE;
}
PS_SET_MOD_DATA(ps_mm_instance);
return SUCCESS;
}
PS_CLOSE_FUNC(mm)
{
PS_SET_MOD_DATA(NULL);
return SUCCESS;
}
PS_READ_FUNC(mm)
{
PS_MM_DATA;
ps_sd *sd;
zend_result ret = FAILURE;
mm_lock(data->mm, MM_LOCK_RD);
sd = ps_sd_lookup(data, key, false);
if (sd) {
*val = zend_string_init(sd->data, sd->datalen, false);
ret = SUCCESS;
}
mm_unlock(data->mm);
return ret;
}
PS_WRITE_FUNC(mm)
{
PS_MM_DATA;
ps_sd *sd;
mm_lock(data->mm, MM_LOCK_RW);
sd = ps_sd_lookup(data, key, true);
if (!sd) {
sd = ps_sd_new(data, key);
ps_mm_debug(("new entry for %s\n", ZSTR_VAL(key)));
}
if (sd) {
if (val->len >= sd->alloclen) {
if (data->mm) {
mm_free(data->mm, sd->data);
}
sd->alloclen = val->len + 1;
sd->data = mm_malloc(data->mm, sd->alloclen);
if (!sd->data) {
ps_sd_destroy(data, sd);
php_error_docref(NULL, E_WARNING, "Cannot allocate new data segment");
sd = NULL;
}
}
if (sd) {
sd->datalen = val->len;
memcpy(sd->data, val->val, val->len);
time(&sd->ctime);
}
}
mm_unlock(data->mm);
return sd ? SUCCESS : FAILURE;
}
PS_DESTROY_FUNC(mm)
{
PS_MM_DATA;
ps_sd *sd;
mm_lock(data->mm, MM_LOCK_RW);
sd = ps_sd_lookup(data, key, false);
if (sd) {
ps_sd_destroy(data, sd);
}
mm_unlock(data->mm);
return SUCCESS;
}
PS_GC_FUNC(mm)
{
PS_MM_DATA;
time_t limit;
ps_sd **ohash, **ehash;
ps_sd *sd, *next;
*nrdels = 0;
ps_mm_debug(("gc\n"));
time(&limit);
limit -= maxlifetime;
mm_lock(data->mm, MM_LOCK_RW);
ehash = data->hash + data->hash_max + 1;
for (ohash = data->hash; ohash < ehash; ohash++) {
for (sd = *ohash; sd; sd = next) {
next = sd->next;
if (sd->ctime < limit) {
ps_mm_debug(("purging %s\n", ZSTR_VAL(sd->key)));
ps_sd_destroy(data, sd);
(*nrdels)++;
}
}
}
mm_unlock(data->mm);
return *nrdels;
}
PS_CREATE_SID_FUNC(mm)
{
zend_string *sid;
int maxfail = 3;
PS_MM_DATA;
do {
sid = php_session_create_id((void **)&data);
/* Check collision */
if (ps_mm_key_exists(data, sid) == SUCCESS) {
if (sid) {
zend_string_release_ex(sid, false);
sid = NULL;
}
if (!(maxfail--)) {
return NULL;
}
}
} while(!sid);
return sid;
}
/*
* Check session ID existence for use_strict_mode support.
* PARAMETERS: PS_VALIDATE_SID_ARGS in php_session.h
* RETURN VALUE: SUCCESS or FAILURE.
*
* Return SUCCESS for valid key(already existing session).
* Return FAILURE for invalid key(non-existing session).
* *mod_data, *key are guaranteed to have non-NULL values.
*/
PS_VALIDATE_SID_FUNC(mm)
{
PS_MM_DATA;
mm_lock(data->mm, MM_LOCK_RD);
zend_result ret = ps_mm_key_exists(data, key)
mm_unlock(data->mm);
return ret;
}
#endif