Skip to content

Commit fde4a95

Browse files
committed
security: pool separation and security option
1 parent af253a3 commit fde4a95

13 files changed

Lines changed: 535 additions & 75 deletions

ext/opcache/ZendAccelerator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ typedef struct _zend_accel_directives {
152152
zend_long memory_consumption;
153153
zend_long static_cache_volatile_size_mb;
154154
zend_long static_cache_pinned_size_mb;
155+
bool static_cache_allow_unsafe_runtime;
155156
zend_long max_accelerated_files;
156157
double max_wasted_percentage;
157158
char *user_blacklist_filename;
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
--TEST--
2+
FPM: OPcache Static Cache is separated between pools
3+
--SKIPIF--
4+
<?php include __DIR__ . '/skipif.inc'; ?>
5+
--FILE--
6+
<?php
7+
8+
require_once __DIR__ . '/tester.inc';
9+
10+
$cfg = <<<EOT
11+
[global]
12+
error_log = {{FILE:LOG}}
13+
[alpha]
14+
listen = {{ADDR[alpha]}}
15+
pm = static
16+
pm.max_children = 1
17+
pm.max_requests = 0
18+
catch_workers_output = yes
19+
[beta]
20+
listen = {{ADDR[beta]}}
21+
pm = static
22+
pm.max_children = 1
23+
pm.max_requests = 0
24+
catch_workers_output = yes
25+
EOT;
26+
27+
$code = <<<'PHP'
28+
<?php
29+
30+
#[OPcache\PinnedStatic]
31+
class FpmPoolSeparatePinnedStatic
32+
{
33+
public static int $value = 0;
34+
}
35+
36+
class FpmPoolSeparatePinnedProperty
37+
{
38+
#[OPcache\PinnedStatic]
39+
public static int $value = 0;
40+
}
41+
42+
class FpmPoolSeparatePinnedMethod
43+
{
44+
#[OPcache\PinnedStatic]
45+
public static function value(?int $value = null): int
46+
{
47+
static $stored = 0;
48+
49+
if ($value !== null) {
50+
$stored = $value;
51+
}
52+
53+
return $stored;
54+
}
55+
}
56+
57+
$action = $_GET['action'] ?? 'fetch';
58+
$pool = $_GET['pool'] ?? 'unknown';
59+
$volatileKey = 'fpm_pool_separate_shared_key';
60+
$pinnedKey = 'fpm_pool_separate_pinned_key';
61+
62+
if ($action === 'seed') {
63+
$base = $pool === 'alpha' ? 100 : 200;
64+
65+
OPcache\volatile_store($volatileKey, $pool . '-volatile');
66+
OPcache\pinned_store($pinnedKey, $pool . '-pinned');
67+
FpmPoolSeparatePinnedStatic::$value = $base + 1;
68+
FpmPoolSeparatePinnedProperty::$value = $base + 2;
69+
FpmPoolSeparatePinnedMethod::value($base + 3);
70+
}
71+
72+
printf(
73+
"%s:%s:%s:%d:%d:%d\n",
74+
$pool,
75+
OPcache\volatile_fetch($volatileKey, 'MISS'),
76+
OPcache\pinned_fetch($pinnedKey, 'MISS'),
77+
FpmPoolSeparatePinnedStatic::$value,
78+
FpmPoolSeparatePinnedProperty::$value,
79+
FpmPoolSeparatePinnedMethod::value()
80+
);
81+
PHP;
82+
83+
function expectPoolState(FPM\Tester $tester, string $pool, string $expected): void
84+
{
85+
$response = $tester->request(
86+
query: 'action=fetch&pool=' . $pool,
87+
address: '{{ADDR[' . $pool . ']}}',
88+
);
89+
$body = trim((string) $response->getBody());
90+
if ($body !== $expected) {
91+
throw new RuntimeException(sprintf(
92+
'Unexpected state for pool %s: expected %s, got %s',
93+
$pool,
94+
$expected,
95+
$body
96+
));
97+
}
98+
}
99+
100+
function seedPool(FPM\Tester $tester, string $pool, string $expected): void
101+
{
102+
$response = $tester->request(
103+
query: 'action=seed&pool=' . $pool,
104+
address: '{{ADDR[' . $pool . ']}}',
105+
);
106+
$body = trim((string) $response->getBody());
107+
if ($body !== $expected) {
108+
throw new RuntimeException(sprintf(
109+
'Unexpected seed state for pool %s: expected %s, got %s',
110+
$pool,
111+
$expected,
112+
$body
113+
));
114+
}
115+
}
116+
117+
$tester = new FPM\Tester($cfg, $code);
118+
$tester->start(iniEntries: [
119+
'opcache.enable' => '1',
120+
'opcache.static_cache.volatile_size_mb' => '32',
121+
'opcache.static_cache.pinned_size_mb' => '32',
122+
'opcache.file_update_protection' => '0',
123+
]);
124+
$tester->expectLogStartNotices();
125+
126+
seedPool($tester, 'alpha', 'alpha:alpha-volatile:alpha-pinned:101:102:103');
127+
expectPoolState($tester, 'alpha', 'alpha:alpha-volatile:alpha-pinned:101:102:103');
128+
expectPoolState($tester, 'beta', 'beta:MISS:MISS:0:0:0');
129+
130+
seedPool($tester, 'beta', 'beta:beta-volatile:beta-pinned:201:202:203');
131+
expectPoolState($tester, 'alpha', 'alpha:alpha-volatile:alpha-pinned:101:102:103');
132+
expectPoolState($tester, 'beta', 'beta:beta-volatile:beta-pinned:201:202:203');
133+
134+
$tester->terminate();
135+
$tester->expectLogTerminatingNotices();
136+
$tester->close();
137+
138+
echo "Done\n";
139+
140+
?>
141+
--EXPECT--
142+
Done
143+
--CLEAN--
144+
<?php
145+
require_once __DIR__ . '/tester.inc';
146+
FPM\Tester::clean();
147+
?>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
OPcache Static Cache is disabled by default for unsafe cgi-fcgi runtime
3+
--EXTENSIONS--
4+
opcache
5+
--CGI--
6+
--INI--
7+
opcache.enable=1
8+
opcache.static_cache.volatile_size_mb=8
9+
opcache.static_cache.pinned_size_mb=8
10+
--FILE--
11+
<?php
12+
13+
echo PHP_SAPI, "\n";
14+
15+
$configuration = opcache_get_configuration();
16+
var_dump($configuration['directives']['opcache.static_cache.allow_unsafe_runtime']);
17+
18+
$volatile = OPcache\volatile_cache_info();
19+
$pinned = OPcache\pinned_cache_info();
20+
21+
echo "volatile\n";
22+
var_dump($volatile->enabled);
23+
var_dump($volatile->available);
24+
var_dump($volatile->failure_reason);
25+
var_dump(OPcache\volatile_store('key', 'volatile'));
26+
27+
echo "pinned\n";
28+
var_dump($pinned->enabled);
29+
var_dump($pinned->available);
30+
var_dump($pinned->failure_reason);
31+
var_dump(OPcache\pinned_store('key', 'pinned'));
32+
33+
?>
34+
--EXPECT--
35+
cgi-fcgi
36+
bool(false)
37+
volatile
38+
bool(true)
39+
bool(false)
40+
string(96) "Static Cache is disabled for this SAPI unless opcache.static_cache.allow_unsafe_runtime=1 is set"
41+
bool(false)
42+
pinned
43+
bool(true)
44+
bool(false)
45+
string(96) "Static Cache is disabled for this SAPI unless opcache.static_cache.allow_unsafe_runtime=1 is set"
46+
bool(false)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
OPcache Static Cache can be enabled explicitly for unsafe cgi-fcgi runtime
3+
--EXTENSIONS--
4+
opcache
5+
--CGI--
6+
--INI--
7+
opcache.enable=1
8+
opcache.static_cache.volatile_size_mb=8
9+
opcache.static_cache.pinned_size_mb=8
10+
opcache.static_cache.allow_unsafe_runtime=1
11+
--FILE--
12+
<?php
13+
14+
echo PHP_SAPI, "\n";
15+
16+
$configuration = opcache_get_configuration();
17+
var_dump($configuration['directives']['opcache.static_cache.allow_unsafe_runtime']);
18+
19+
$volatile = OPcache\volatile_cache_info();
20+
$pinned = OPcache\pinned_cache_info();
21+
22+
echo "volatile\n";
23+
var_dump($volatile->enabled);
24+
var_dump($volatile->available);
25+
var_dump($volatile->failure_reason);
26+
var_dump(OPcache\volatile_store('key', 'volatile'));
27+
28+
echo "pinned\n";
29+
var_dump($pinned->enabled);
30+
var_dump($pinned->available);
31+
var_dump($pinned->failure_reason);
32+
var_dump(OPcache\pinned_store('key', 'pinned'));
33+
34+
?>
35+
--EXPECT--
36+
cgi-fcgi
37+
bool(true)
38+
volatile
39+
bool(true)
40+
bool(true)
41+
NULL
42+
bool(true)
43+
pinned
44+
bool(true)
45+
bool(true)
46+
NULL
47+
bool(true)

ext/opcache/zend_accelerator_module.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ ZEND_INI_BEGIN()
372372
STD_PHP_INI_ENTRY("opcache.memory_consumption" , "128" , PHP_INI_SYSTEM, OnUpdateMemoryConsumption, accel_directives.memory_consumption, zend_accel_globals, accel_globals)
373373
STD_PHP_INI_ENTRY("opcache.static_cache.volatile_size_mb", "8", PHP_INI_SYSTEM, OnUpdateStaticCacheVolatileSizeMb, accel_directives.static_cache_volatile_size_mb, zend_accel_globals, accel_globals)
374374
STD_PHP_INI_ENTRY("opcache.static_cache.pinned_size_mb", "8", PHP_INI_SYSTEM, OnUpdateStaticCachePinnedSizeMb, accel_directives.static_cache_pinned_size_mb, zend_accel_globals, accel_globals)
375+
STD_PHP_INI_BOOLEAN("opcache.static_cache.allow_unsafe_runtime", "0", PHP_INI_SYSTEM, OnUpdateBool, accel_directives.static_cache_allow_unsafe_runtime, zend_accel_globals, accel_globals)
375376
STD_PHP_INI_ENTRY("opcache.interned_strings_buffer", "8" , PHP_INI_SYSTEM, OnUpdateInternedStringsBuffer, accel_directives.interned_strings_buffer, zend_accel_globals, accel_globals)
376377
STD_PHP_INI_ENTRY("opcache.max_accelerated_files" , "10000", PHP_INI_SYSTEM, OnUpdateMaxAcceleratedFiles, accel_directives.max_accelerated_files, zend_accel_globals, accel_globals)
377378
STD_PHP_INI_ENTRY("opcache.max_wasted_percentage" , "5" , PHP_INI_SYSTEM, OnUpdateMaxWastedPercentage, accel_directives.max_wasted_percentage, zend_accel_globals, accel_globals)
@@ -944,6 +945,7 @@ ZEND_FUNCTION(opcache_get_configuration)
944945
add_assoc_long(&directives, "opcache.memory_consumption", ZCG(accel_directives).memory_consumption);
945946
add_assoc_long(&directives, "opcache.static_cache.volatile_size_mb", ZCG(accel_directives).static_cache_volatile_size_mb);
946947
add_assoc_long(&directives, "opcache.static_cache.pinned_size_mb", ZCG(accel_directives).static_cache_pinned_size_mb);
948+
add_assoc_bool(&directives, "opcache.static_cache.allow_unsafe_runtime", ZCG(accel_directives).static_cache_allow_unsafe_runtime);
947949
add_assoc_long(&directives, "opcache.interned_strings_buffer",ZCG(accel_directives).interned_strings_buffer);
948950
add_assoc_long(&directives, "opcache.max_accelerated_files", ZCG(accel_directives).max_accelerated_files);
949951
add_assoc_double(&directives, "opcache.max_wasted_percentage", ZCG(accel_directives).max_wasted_percentage);

0 commit comments

Comments
 (0)