Skip to content

Commit 129303e

Browse files
committed
add php_server's env vars to sandboxed environment
keep track of separate prepared_env set for a php_server correctly exposes env vars into $_ENV when E is in `variables_order` also fixes the problem of lazy server eval I didn't think about last commit
1 parent 61077e1 commit 129303e

5 files changed

Lines changed: 81 additions & 2 deletions

File tree

cgi.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ package frankenphp
44
// #cgo nocallback frankenphp_register_variable_safe
55
// #cgo nocallback frankenphp_register_known_variable
66
// #cgo nocallback frankenphp_init_persistent_string
7+
// #cgo nocallback frankenphp_add_to_sandboxed_env
78
// #cgo noescape frankenphp_register_server_vars
89
// #cgo noescape frankenphp_register_variable_safe
910
// #cgo noescape frankenphp_register_known_variable
1011
// #cgo noescape frankenphp_init_persistent_string
12+
// #cgo noescape frankenphp_add_to_sandboxed_env
1113
// #include "frankenphp.h"
1214
// #include <php_variables.h>
1315
import "C"
@@ -180,6 +182,13 @@ func addPreparedEnvToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
180182
fc.env = nil
181183
}
182184

185+
// addPreparedEnvToSandbox exposes fc.env to getenv() before any PHP code runs.
186+
func addPreparedEnvToSandbox(fc *frankenPHPContext) {
187+
for k, v := range fc.env {
188+
C.frankenphp_add_to_sandboxed_env(toUnsafeChar(k), C.size_t(len(k)-1), toUnsafeChar(v), C.size_t(len(v)))
189+
}
190+
}
191+
183192
//export go_register_server_variables
184193
func go_register_server_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
185194
thread := phpThreads[threadIndex]
@@ -298,6 +307,8 @@ func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info)
298307
return nil
299308
}
300309

310+
addPreparedEnvToSandbox(fc)
311+
301312
if m, ok := cStringHTTPMethods[request.Method]; ok {
302313
info.request_method = m
303314
} else {

frankenphp.c

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ HashTable *main_thread_env = NULL;
9292
__thread uintptr_t thread_index;
9393
__thread bool is_worker_thread = false;
9494
__thread HashTable *sandboxed_env = NULL;
95+
/* prepared_env holds entries from php(_server)'s `env KEY VAL`,
96+
* so they can be merged into $_ENV when 'E' is in
97+
* variables_order. Separate from putenv() so we don't leak into $_ENV */
98+
__thread HashTable *prepared_env = NULL;
9599

96100
/* Published via SG(server_context) so ext-parallel children, which inherit
97101
* the parent's SG(server_context), can route SAPI callbacks back to the
@@ -429,9 +433,16 @@ bool frankenphp_shutdown_dummy_request(void) {
429433
}
430434

431435
void get_full_env(zval *track_vars_array) {
432-
zend_hash_extend(Z_ARR_P(track_vars_array),
433-
zend_hash_num_elements(main_thread_env), 0);
436+
size_t total = zend_hash_num_elements(main_thread_env);
437+
if (prepared_env != NULL) {
438+
// perf: doesn't matter if we get the exact count, just >= needed
439+
total += zend_hash_num_elements(prepared_env);
440+
}
441+
zend_hash_extend(Z_ARR_P(track_vars_array), total, 0);
434442
zend_hash_copy(Z_ARR_P(track_vars_array), main_thread_env, NULL);
443+
if (prepared_env != NULL) {
444+
zend_hash_copy(Z_ARR_P(track_vars_array), prepared_env, NULL);
445+
}
435446
}
436447

437448
/* Adapted from php_request_startup() */
@@ -1239,6 +1250,31 @@ static inline void reset_sandboxed_environment() {
12391250
zend_hash_release(sandboxed_env);
12401251
sandboxed_env = NULL;
12411252
}
1253+
if (prepared_env != NULL) {
1254+
zend_hash_release(prepared_env);
1255+
prepared_env = NULL;
1256+
}
1257+
}
1258+
1259+
/* Adds a key/value pair to the per-thread sandboxed environment so it becomes
1260+
* visible to getenv()/$_ENV. Used to expose env vars declared in the php(_server)
1261+
* directive, which would otherwise only appear in $_SERVER. */
1262+
void frankenphp_add_to_sandboxed_env(char *name, size_t name_len, char *val,
1263+
size_t val_len) {
1264+
if (sandboxed_env == NULL) {
1265+
sandboxed_env = zend_array_dup(main_thread_env);
1266+
}
1267+
zval zv = {0};
1268+
ZVAL_STRINGL(&zv, val, val_len);
1269+
zend_hash_str_update(sandboxed_env, name, name_len, &zv);
1270+
1271+
if (prepared_env == NULL) {
1272+
ALLOC_HASHTABLE(prepared_env);
1273+
zend_hash_init(prepared_env, 8, NULL, ZVAL_PTR_DTOR, 0);
1274+
}
1275+
zval zv2 = {0};
1276+
ZVAL_STRINGL(&zv2, val, val_len);
1277+
zend_hash_str_update(prepared_env, name, name_len, &zv2);
12421278
}
12431279

12441280
static void *php_thread(void *arg) {

frankenphp.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ void frankenphp_register_variable_safe(char *key, char *var, size_t val_len,
202202
zval *track_vars_array);
203203
void frankenphp_register_server_vars(zval *track_vars_array,
204204
frankenphp_server_vars vars);
205+
void frankenphp_add_to_sandboxed_env(char *name, size_t name_len, char *val,
206+
size_t val_len);
205207

206208
zend_string *frankenphp_init_persistent_string(const char *string, size_t len);
207209
int frankenphp_reset_opcache(void);

frankenphp_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,25 @@ func TestEnvIsNotResetInWorkerMode(t *testing.T) {
767767
}, &testOptions{workerScript: "env/remember-env.php"})
768768
}
769769

770+
// reproduction of https://github.com/php/frankenphp/issues/1674
771+
func TestPreparedEnvIsVisibleToGetenv_module(t *testing.T) {
772+
testPreparedEnvIsVisibleToGetenv(t, &testOptions{nbParallelRequests: 1})
773+
}
774+
func TestPreparedEnvIsVisibleToGetenv_worker(t *testing.T) {
775+
testPreparedEnvIsVisibleToGetenv(t, &testOptions{
776+
workerScript: "env/prepared-env-getenv.php",
777+
})
778+
}
779+
func testPreparedEnvIsVisibleToGetenv(t *testing.T, opts *testOptions) {
780+
opts.requestOpts = append(opts.requestOpts,
781+
frankenphp.WithRequestEnv(map[string]string{"FRANKENPHP_TEST_PHP_SERVER_ENV_IN_GETENV": "hello"}),
782+
)
783+
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, _ int) {
784+
body, _ := testGet("http://example.com/env/prepared-env-getenv.php", handler, t)
785+
assert.Equal(t, "getenv='hello'\nserver='hello'\n", body)
786+
}, opts)
787+
}
788+
770789
// reproduction of https://github.com/php/frankenphp/issues/1061
771790
func TestModificationsToEnvPersistAcrossRequests(t *testing.T) {
772791
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
require_once __DIR__ . '/../_executor.php';
4+
5+
return function () {
6+
// Variables declared via the env subdirective in the php(_server) directive
7+
// (or via WithRequestEnv) must be exposed to both $_SERVER and getenv().
8+
// See https://github.com/php/frankenphp/issues/1674
9+
echo "getenv=" . var_export(getenv('FRANKENPHP_TEST_PHP_SERVER_ENV_IN_GETENV'), true) . "\n";
10+
echo "server=" . var_export($_SERVER['FRANKENPHP_TEST_PHP_SERVER_ENV_IN_GETENV'] ?? null, true) . "\n";
11+
};

0 commit comments

Comments
 (0)