Skip to content

Commit 8f4412c

Browse files
perf: move sandboxed environment to the C side (#2058)
This PR uses `zend_array_dup` to simplify and optimize the environment sandboxing logic. It also guarantees no environment leakage on FrankenPHP restarts.
1 parent 25ed020 commit 8f4412c

File tree

14 files changed

+158
-213
lines changed

14 files changed

+158
-213
lines changed

caddy/caddy_test.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ func escapeMetricLabel(s string) string {
6161
return strings.ReplaceAll(s, "\\", "\\\\")
6262
}
6363

64+
func TestMain(m *testing.M) {
65+
// setup custom environment vars for TestOsEnv
66+
if os.Setenv("ENV1", "value1") != nil || os.Setenv("ENV2", "value2") != nil {
67+
fmt.Println("Failed to set environment variables for tests")
68+
os.Exit(1)
69+
}
70+
71+
os.Exit(m.Run())
72+
}
73+
6474
func TestPHP(t *testing.T) {
6575
var wg sync.WaitGroup
6676
tester := caddytest.NewTester(t)
@@ -957,9 +967,6 @@ func testSingleIniConfiguration(tester *caddytest.Tester, key string, value stri
957967
}
958968

959969
func TestOsEnv(t *testing.T) {
960-
require.NoError(t, os.Setenv("ENV1", "value1"))
961-
require.NoError(t, os.Setenv("ENV2", "value2"))
962-
963970
tester := caddytest.NewTester(t)
964971
tester.InitServer(`
965972
{

caddy/mercure.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ package caddy
55
import (
66
"encoding/json"
77
"errors"
8-
"os"
98
"github.com/caddyserver/caddy/v2"
109
"github.com/caddyserver/caddy/v2/caddyconfig"
1110
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
1211
"github.com/dunglas/frankenphp"
1312
"github.com/dunglas/mercure"
1413
mercureCaddy "github.com/dunglas/mercure/caddy"
14+
"os"
1515
)
1616

1717
func init() {
@@ -67,5 +67,5 @@ func createMercureRoute() (caddyhttp.Route, error) {
6767
},
6868
}
6969

70-
return mercureRoute, nil;
70+
return mercureRoute, nil
7171
}

caddy/php-server.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,8 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) {
253253
if mercure {
254254
mercureRoute, err := createMercureRoute()
255255
if err != nil {
256-
return caddy.ExitCodeFailedStartup, err
257-
}
256+
return caddy.ExitCodeFailedStartup, err
257+
}
258258

259259
subroute.Routes = append(caddyhttp.RouteList{mercureRoute}, subroute.Routes...)
260260
}

env.go

Lines changed: 14 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -3,114 +3,32 @@ package frankenphp
33
// #cgo nocallback frankenphp_init_persistent_string
44
// #cgo noescape frankenphp_init_persistent_string
55
// #include "frankenphp.h"
6-
// #include <Zend/zend_API.h>
6+
// #include "types.h"
77
import "C"
88
import (
99
"os"
1010
"strings"
11-
"unsafe"
1211
)
1312

14-
func initializeEnv() map[string]*C.zend_string {
15-
env := os.Environ()
16-
envMap := make(map[string]*C.zend_string, len(env))
17-
18-
for _, envVar := range env {
13+
//export go_init_os_env
14+
func go_init_os_env(mainThreadEnv *C.zend_array) {
15+
for _, envVar := range os.Environ() {
1916
key, val, _ := strings.Cut(envVar, "=")
20-
envMap[key] = C.frankenphp_init_persistent_string(toUnsafeChar(val), C.size_t(len(val)))
21-
}
22-
23-
return envMap
24-
}
25-
26-
// get the main thread env or the thread specific env
27-
func getSandboxedEnv(thread *phpThread) map[string]*C.zend_string {
28-
if thread.sandboxedEnv != nil {
29-
return thread.sandboxedEnv
30-
}
31-
32-
return mainThread.sandboxedEnv
33-
}
34-
35-
func clearSandboxedEnv(thread *phpThread) {
36-
if thread.sandboxedEnv == nil {
37-
return
38-
}
39-
40-
for key, val := range thread.sandboxedEnv {
41-
valInMainThread, ok := mainThread.sandboxedEnv[key]
42-
if !ok || val != valInMainThread {
43-
C.free(unsafe.Pointer(val))
44-
}
45-
}
46-
47-
thread.sandboxedEnv = nil
48-
}
49-
50-
// if an env var already exists, it needs to be freed
51-
func removeEnvFromThread(thread *phpThread, key string) {
52-
valueInThread, existsInThread := thread.sandboxedEnv[key]
53-
if !existsInThread {
54-
return
55-
}
56-
57-
valueInMainThread, ok := mainThread.sandboxedEnv[key]
58-
if !ok || valueInThread != valueInMainThread {
59-
C.free(unsafe.Pointer(valueInThread))
60-
}
61-
62-
delete(thread.sandboxedEnv, key)
63-
}
64-
65-
// copy the main thread env to the thread specific env
66-
func cloneSandboxedEnv(thread *phpThread) {
67-
if thread.sandboxedEnv != nil {
68-
return
69-
}
70-
thread.sandboxedEnv = make(map[string]*C.zend_string, len(mainThread.sandboxedEnv))
71-
for key, value := range mainThread.sandboxedEnv {
72-
thread.sandboxedEnv[key] = value
17+
zkey := C.frankenphp_init_persistent_string(toUnsafeChar(key), C.size_t(len(key)))
18+
zStr := C.frankenphp_init_persistent_string(toUnsafeChar(val), C.size_t(len(val)))
19+
C.__hash_update_string__(mainThreadEnv, zkey, zStr)
7320
}
7421
}
7522

7623
//export go_putenv
77-
func go_putenv(threadIndex C.uintptr_t, str *C.char, length C.int) C.bool {
78-
thread := phpThreads[threadIndex]
79-
envString := C.GoStringN(str, length)
80-
cloneSandboxedEnv(thread)
81-
82-
// Check if '=' is present in the string
83-
if key, val, found := strings.Cut(envString, "="); found {
84-
removeEnvFromThread(thread, key)
85-
thread.sandboxedEnv[key] = C.frankenphp_init_persistent_string(toUnsafeChar(val), C.size_t(len(val)))
86-
return os.Setenv(key, val) == nil
87-
}
88-
89-
// No '=', unset the environment variable
90-
removeEnvFromThread(thread, envString)
91-
return os.Unsetenv(envString) == nil
92-
}
93-
94-
//export go_getfullenv
95-
func go_getfullenv(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
96-
thread := phpThreads[threadIndex]
97-
env := getSandboxedEnv(thread)
98-
99-
for key, val := range env {
100-
C.add_assoc_str_ex(trackVarsArray, toUnsafeChar(key), C.size_t(len(key)), val)
101-
}
102-
}
103-
104-
//export go_getenv
105-
func go_getenv(threadIndex C.uintptr_t, name *C.char) (C.bool, *C.zend_string) {
106-
thread := phpThreads[threadIndex]
24+
func go_putenv(name *C.char, nameLen C.int, val *C.char, valLen C.int) C.bool {
25+
goName := C.GoStringN(name, nameLen)
10726

108-
// Get the environment variable value
109-
envValue, exists := getSandboxedEnv(thread)[C.GoString(name)]
110-
if !exists {
111-
// Environment variable does not exist
112-
return false, nil // Return 0 to indicate failure
27+
if val == nil {
28+
// If no "=" is present, unset the environment variable
29+
return C.bool(os.Unsetenv(goName) == nil)
11330
}
11431

115-
return true, envValue // Return 1 to indicate success
32+
goVal := C.GoStringN(val, valLen)
33+
return C.bool(os.Setenv(goName, goVal) == nil)
11634
}

0 commit comments

Comments
 (0)