PHP, bien qu’interprété au niveau utilisateur, repose sur une couche C profonde où la gestion mémoire devient critique. À ce stade, la sécurité n’est plus garantie par le moteur — elle dépend entièrement de la rigueur des développeurs impliquant des allocations, des libérations et des accès valides aux structures internes.
Sécurité multithread et macros TSRM
Dans les environnements threadés (ZTS), l’accès aux données globales doit être isolé par contexte. Le système TSRM fournit des macros pour gérer ce partage en toute sécurité :
#define TSRMLS_FETCH() void ***tsrm_ls = (void ***)ts_resource_ex(0, NULL)
#define TSRMLS_D void ***tsrm_ls
#define TSRMLS_DC , TSRMLS_D
#define TSRMLS_C tsrm_ls
#define TSRMLS_CC , TSRMLS_C
Ces macros suivent une logique cohérente : TSRMLS_D déclare le pointeur de contexte dans la signature de fonction, TSRMLS_DC l’ajoute avec une virgule ; TSRMLS_C et TSRMLS_CC le passent comme argument lors de l’appel. Par exemple :
int process_data(uint32_t id, const char *input TSRMLS_DC);
process_data(101, "payload" TSRMLS_CC);
L’appel à TSRMLS_FETCH() coûte en performance. Il est donc recommandé de l’invoquer une seule fois par portée, impérativement avant toute autre instruction — notamment pour compatibilité avec les compilateurs C++.
Cycle de vie d’un module PHP
Qu’il s’exécute en mode CLI ou via un SAPI web (Apache, FPM), chaque module PHP traverse des phases bien définies :
PHP_MINIT_FUNCTION: initialisation statqiue du module (chargement, registre de fonctions)PHP_MSHUTDOWN_FUNCTION: nettoyage global (libération de ressources persistantes)PHP_RINIT_FUNCTION: préparation par requête (ex. initialisation de variables locales)PHP_RSHUTDOWN_FUNCTION: finalisation par requête (fermeture temporaire, vidage de buffers)PHP_GINIT_FUNCTION/PHP_GSHUTDOWN_FUNCTION: gestion des variables globales thread-safePHP_MINFO_FUNCTION: affichage des informations dansphpinfo()
Voici un exemple minimaliste illustrant l’enregistrement temporel des événements :
static time_t module_start;
static time_t request_start;
PHP_MINIT_FUNCTION(sample) {
module_start = time(NULL);
return SUCCESS;
}
PHP_RINIT_FUNCTION(sample) {
request_start = time(NULL);
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(sample) {
FILE *log = fopen("/tmp/module_shutdown.log", "a");
fprintf(log, "Shutdown at %ld\n", time(NULL));
fclose(log);
return SUCCESS;
}
Débogage des erreurs de segmentation
Sur Linux, une erreur d’accès mémoire déclenche un segmentation fault. Si le noyau est configuré pour générer des fichiers core, ceux-ci permettent une analyse post-mortem précise avec gdb :
- Vérifier la limite actuelle :
ulimit -c - Activer les dumps illimités :
ulimit -c unlimited - Exécuter le script défaillant (ex.
php crash.php) - Identifier le fichier
core.*généré - Analyser avec
gdb /usr/bin/php core.12345
Pour rendre cela permanent, ajouter ulimit -c unlimited dans ~/.bashrc ou /etc/profile. Le format des noms de fichiers core peut aussi être personnalisé via /proc/sys/kernel/core_pattern.
Macros d’accès aux structures globales
Le cœur PHP expose plusieurs espaces de données via des macros abstraites :
SG(v)→ Accès aux données SAPI (sapi_globals_struct) : URI, en-têtes, paramètresEG(v)→ Environnement d’exécution (_zend_execution_globals) : tables de symboles, pile d’appelsCG(v)→ Données de compilation (fonctions prédéfinies, opcodes)PG(v)→ Configuration issue dephp.ini(ex.memory_limit)FG(v)→ État spécifique aux extensions standard (gestion de fichiers, flux)
Exemple concret d’accès à l’URI courante :
const char *uri = SG(request_info).request_uri;
Manipulation des zval et typage
Chaque valeur PHP est encapsulée dans une structure zval. L’accès aux types et contenus se fait via des macros spécialisées :
| Type | Macro (zval) | Macro (zval*) | Macro (zval**) |
|---|---|---|---|
| Entier | Z_LVAL(z) |
Z_LVAL_P(p) |
Z_LVAL_PP(pp) |
| Chaîne | Z_STRVAL(z)/Z_STRLEN(z) |
Z_STRVAL_P(p)/Z_STRLEN_P(p) |
Z_STRVAL_PP(pp)/Z_STRLEN_PP(pp) |
| Table de hachage | Z_ARRVAL(z) |
Z_ARRVAL_P(p) |
Z_ARRVAL_PP(pp) |
La fonction gettype() illustre leur usage :
PHP_FUNCTION(gettype) {
zval **input;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z", &input) == FAILURE) {
RETURN_FALSE;
}
switch (Z_TYPE_PP(input)) {
case IS_NULL: RETURN_STRING("NULL", 1); break;
case IS_BOOL: RETURN_STRING("boolean", 1); break;
case IS_LONG: RETURN_STRING("integer", 1); break;
case IS_DOUBLE: RETURN_STRING("double", 1); break;
case IS_STRING: RETURN_STRING("string", 1); break;
case IS_ARRAY: RETURN_STRING("array", 1); break;
case IS_OBJECT: RETURN_STRING("object", 1); break;
default: RETURN_STRING("unknown", 1);
}
}
Déclaration de constantes et variables globales
Les constantes sont enregistrées durant l’initialisation du module ou à chaque requête :
PHP_MINIT_FUNCTION(myext) {
REGISTER_LONG_CONSTANT("MYEXT_VERSION", 2024, CONST_CS | CONST_PERSISTENT);
REGISTER_STRING_CONSTANT("MYEXT_NAME", "engine", CONST_CS);
return SUCCESS;
}
PHP_RINIT_FUNCTION(myext) {
char rand_id[16];
snprintf(rand_id, sizeof(rand_id), "%u", (unsigned int)rand());
REGISTER_STRING_CONSTANT("MYEXT_SESSION_ID", estrdup(rand_id), CONST_CS);
return SUCCESS;
}
Pour les variables superglobalse ($_GET, $_SERVER), PHP les stocke dans la table de symboles globale accessible via &EG(symbol_table). Un accès typique à $_SERVER['HTTP_USER_AGENT'] suit ce schéma :
zval **server, **ua;
if (zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **)&server) == SUCCESS &&
Z_TYPE_PP(server) == IS_ARRAY) {
HashTable *ht = Z_ARRVAL_PP(server);
if (zend_hash_find(ht, "HTTP_USER_AGENT", sizeof("HTTP_USER_AGENT"), (void **)&ua) == SUCCESS) {
RETVAL_STRINGL(Z_STRVAL_PP(ua), Z_STRLEN_PP(ua), 1);
}
}
Intégration de bibliothèques externes
Pour lier une extension à une bibliothèque comme cURL, le script config.m4 doit détecter les chemins d’en-têtes et de bibliothèques :
PHP_ARG_WITH(curl, for cURL support,
[ --with-curl[=DIR] Include cURL support])
if test "$PHP_CURL" != "no"; then
SEARCH_PATH="/usr/local /usr"
for i in $SEARCH_PATH; do
if test -f "$i/include/curl/curl.h"; then
CURL_DIR=$i
PHP_ADD_INCLUDE($CURL_DIR/include)
break
fi
done
if test -z "$CURL_DIR"; then
AC_MSG_ERROR([cURL headers not found.])
fi
PHP_CHECK_LIBRARY(curl, curl_global_init,
[PHP_ADD_LIBRARY_WITH_PATH(curl, $CURL_DIR/lib, CURL_SHARED_LIBADD)],
[AC_MSG_ERROR([cURL library not found.])],
[-L$CURL_DIR/lib -lcurl]
)
PHP_SUBST(CURL_SHARED_LIBADD)
fi
Retours de fonctions et gestion des zval
Les macros RETURN_* simplifient la sortie de valeurs depuis une fonction C :
PHP_FUNCTION(add_numbers) {
long a, b;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &a, &b) == FAILURE) {
RETURN_FALSE;
}
RETURN_LONG(a + b);
}
PHP_FUNCTION(return_array) {
zval *arr;
MAKE_STD_ZVAL(arr);
array_init(arr);
add_assoc_long(arr, "sum", 42);
add_assoc_string(arr, "status", "ok", 1);
RETURN_ZVAL(arr, 1, 1);
}
Parcours des tableaux (HashTable)
Les tableaux PHP sont implémentés comme des HashTable. Leur itération sécurisée utilise des pointeurs internes :
HashTable *ht = Z_ARRVAL_P(input_array);
HashPosition pos;
zval **entry;
for (zend_hash_internal_pointer_reset_ex(ht, &pos);
zend_hash_get_current_data_ex(ht, (void**)&entry, &pos) == SUCCESS;
zend_hash_move_forward_ex(ht, &pos)) {
if (Z_TYPE_PP(entry) == IS_STRING) {
php_printf("String: %s\n", Z_STRVAL_PP(entry));
}
}
Des fonctions comme zend_hash_num_elements() ou zend_hash_index_update() permettent également des opérations directes sur les clés numériques ou chaînes.