Skip to content

Commit 47e1bea

Browse files
committed
fix: fix SEGV on many patterns
1 parent 6e5f7a0 commit 47e1bea

6 files changed

Lines changed: 302 additions & 21 deletions
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
--TEST--
2+
UserCache\Cache: clear() survives request-local slot destructors that re-enter clear()/deletePool()
3+
--INI--
4+
user_cache.enable=1
5+
user_cache.enable_cli=1
6+
opcache.file_cache_only=0
7+
user_cache.shm_size=16M
8+
--FILE--
9+
<?php
10+
class ClearReentrantDtor
11+
{
12+
public static bool $armed = false;
13+
14+
public int $n = 1;
15+
16+
public function __destruct()
17+
{
18+
if (self::$armed) {
19+
UserCache\Cache::getPool('clear-dtor-reentry')->clear();
20+
}
21+
}
22+
}
23+
24+
class DeletePoolReentrantDtor
25+
{
26+
public static bool $armed = false;
27+
28+
public int $n = 2;
29+
30+
public function __destruct()
31+
{
32+
if (self::$armed) {
33+
self::$armed = false;
34+
35+
UserCache\Cache::deletePool('clear-dtor-reentry');
36+
}
37+
}
38+
}
39+
40+
function seed_request_local_slot(UserCache\Cache $cache, string $key, object $value): void
41+
{
42+
$cache->store($key, $value);
43+
44+
$marked = $cache->fetch($key);
45+
unset($marked);
46+
47+
$seeded = $cache->fetch($key);
48+
unset($seeded);
49+
}
50+
51+
$cache = UserCache\Cache::getPool('clear-dtor-reentry');
52+
$cache->clear();
53+
54+
seed_request_local_slot($cache, 'single', new ClearReentrantDtor());
55+
56+
ClearReentrantDtor::$armed = true;
57+
var_dump($cache->clear());
58+
ClearReentrantDtor::$armed = false;
59+
60+
var_dump($cache->has('single'));
61+
62+
foreach (['multi-1', 'multi-2', 'multi-3'] as $key) {
63+
seed_request_local_slot($cache, $key, new ClearReentrantDtor());
64+
}
65+
66+
ClearReentrantDtor::$armed = true;
67+
var_dump($cache->clear());
68+
ClearReentrantDtor::$armed = false;
69+
70+
var_dump($cache->has('multi-1'));
71+
var_dump($cache->has('multi-3'));
72+
73+
$cache = UserCache\Cache::getPool('clear-dtor-reentry');
74+
seed_request_local_slot($cache, 'delete-pool', new DeletePoolReentrantDtor());
75+
76+
DeletePoolReentrantDtor::$armed = true;
77+
var_dump(UserCache\Cache::deletePool('clear-dtor-reentry'));
78+
DeletePoolReentrantDtor::$armed = false;
79+
80+
var_dump(UserCache\Cache::getPool('clear-dtor-reentry')->has('delete-pool'));
81+
82+
echo "done\n";
83+
?>
84+
--EXPECT--
85+
bool(true)
86+
bool(false)
87+
bool(true)
88+
bool(false)
89+
bool(false)
90+
bool(true)
91+
bool(false)
92+
done
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
UserCache\Cache: object with an empty property name decodes without crashing
3+
--INI--
4+
user_cache.enable=1
5+
user_cache.enable_cli=1
6+
opcache.file_cache_only=0
7+
user_cache.shm_size=16M
8+
--FILE--
9+
<?php
10+
#[AllowDynamicProperties]
11+
class UserCacheEmptyPropertyNameBox
12+
{
13+
public int $declared = 1;
14+
}
15+
16+
$cache = UserCache\Cache::getPool('object-empty-property-name');
17+
$cache->clear();
18+
19+
$plain = (object) ['' => 'empty-name'];
20+
var_dump($cache->store('plain', $plain));
21+
var_dump($cache->fetch('plain', 'default'));
22+
var_dump($cache->fetch('plain', 'default'));
23+
24+
$box = new UserCacheEmptyPropertyNameBox();
25+
$box->{''} = 'empty-name';
26+
var_dump($cache->store('box', $box));
27+
var_dump($cache->fetch('box', 'default'));
28+
?>
29+
--EXPECTF--
30+
bool(true)
31+
object(stdClass)#%d (1) {
32+
[""]=>
33+
string(10) "empty-name"
34+
}
35+
object(stdClass)#%d (1) {
36+
[""]=>
37+
string(10) "empty-name"
38+
}
39+
bool(true)
40+
object(UserCacheEmptyPropertyNameBox)#%d (2) {
41+
["declared"]=>
42+
int(1)
43+
[""]=>
44+
string(10) "empty-name"
45+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
UserCache\Cache: replacing a request-local slot survives a destructor that clears the pool
3+
--INI--
4+
user_cache.enable=1
5+
user_cache.enable_cli=1
6+
opcache.file_cache_only=0
7+
user_cache.shm_size=16M
8+
--FILE--
9+
<?php
10+
class SlotReplaceDtor
11+
{
12+
public int $n = 1;
13+
public static bool $armed = false;
14+
15+
public function __destruct()
16+
{
17+
if (self::$armed) {
18+
UserCache\Cache::getPool('slot-replace')->clear();
19+
}
20+
}
21+
}
22+
23+
$cache = UserCache\Cache::getPool('slot-replace');
24+
$cache->clear();
25+
26+
$obj = new SlotReplaceDtor();
27+
var_dump($cache->store('k', $obj));
28+
29+
$first = $cache->fetch('k');
30+
$second = $cache->fetch('k');
31+
var_dump($second->n);
32+
33+
SlotReplaceDtor::$armed = true;
34+
var_dump($cache->store('k', str_repeat('a', 300)));
35+
SlotReplaceDtor::$armed = false;
36+
37+
var_dump($cache->fetch('k'));
38+
echo "done\n";
39+
?>
40+
--EXPECT--
41+
bool(true)
42+
int(1)
43+
bool(true)
44+
NULL
45+
done

ext/user_cache/user_cache_alloc_posix.c

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#ifdef PHP_USER_CACHE_USE_SHM_OPEN
1818

19+
#include <errno.h>
1920
#include <stdio.h>
2021
#include <stdlib.h>
2122
#include <unistd.h>
@@ -24,17 +25,47 @@
2425
#include <sys/stat.h>
2526
#include <sys/mman.h>
2627

28+
#include "ext/random/php_random_csprng.h"
29+
30+
#define PHP_USER_CACHE_POSIX_SHM_NAME_PREFIX "/php_uc."
31+
#define PHP_USER_CACHE_POSIX_SHM_NAME_BYTES 10
32+
#define PHP_USER_CACHE_POSIX_SHM_NAME_ATTEMPTS 5
33+
2734
typedef struct {
2835
php_user_cache_shm_segment common;
2936
int shm_fd;
3037
} php_user_cache_shm_segment_posix;
3138

39+
static bool user_cache_alloc_posix_segment_name(char *buf, size_t buf_size)
40+
{
41+
static const char hexits[] = "0123456789abcdef";
42+
unsigned char random_bytes[PHP_USER_CACHE_POSIX_SHM_NAME_BYTES];
43+
char hex[sizeof(random_bytes) * 2 + 1];
44+
size_t i;
45+
46+
if (php_random_bytes_silent(random_bytes, sizeof(random_bytes)) == FAILURE) {
47+
return false;
48+
}
49+
50+
for (i = 0; i < sizeof(random_bytes); i++) {
51+
hex[i * 2] = hexits[random_bytes[i] >> 4];
52+
hex[(i * 2) + 1] = hexits[random_bytes[i] & 0xf];
53+
}
54+
hex[sizeof(hex) - 1] = '\0';
55+
56+
snprintf(buf, buf_size, PHP_USER_CACHE_POSIX_SHM_NAME_PREFIX "%s", hex);
57+
58+
return true;
59+
}
60+
3261
static int user_cache_alloc_posix_create_segments(size_t requested_size, php_user_cache_shm_segment_posix ***shared_segments_p, int *shared_segments_count, const char **error_in)
3362
{
3463
php_user_cache_shm_segment_posix *shared_segment;
35-
int shared_segment_flags = O_RDWR | O_CREAT | O_TRUNC;
36-
char shared_segment_name[sizeof("/php_user_cache.") + 20];
3764
mode_t shared_segment_mode = 0600;
65+
/* O_EXCL: never adopt an object somebody else created under this name. */
66+
int shared_segment_flags = O_RDWR | O_CREAT | O_EXCL,
67+
shared_segment_fd = -1, shared_segment_attempt;
68+
char shared_segment_name[sizeof(PHP_USER_CACHE_POSIX_SHM_NAME_PREFIX) + (PHP_USER_CACHE_POSIX_SHM_NAME_BYTES * 2)];
3869

3970
#if defined(HAVE_SHM_CREATE_LARGEPAGE)
4071
/* Prefer the largest compatible page size. Capture the getpagesizes()
@@ -69,30 +100,52 @@ static int user_cache_alloc_posix_create_segments(size_t requested_size, php_use
69100
shared_segment = (php_user_cache_shm_segment_posix *)((char *)(*shared_segments_p) + sizeof(void *));
70101
(*shared_segments_p)[0] = shared_segment;
71102

72-
snprintf(shared_segment_name, sizeof(shared_segment_name), "/php_user_cache.%d", getpid());
103+
for (shared_segment_attempt = 0; shared_segment_attempt < PHP_USER_CACHE_POSIX_SHM_NAME_ATTEMPTS; shared_segment_attempt++) {
104+
if (!user_cache_alloc_posix_segment_name(shared_segment_name, sizeof(shared_segment_name))) {
105+
*error_in = "php_random_bytes";
106+
107+
return PHP_USER_CACHE_ALLOC_FAILURE;
108+
}
109+
73110
#if defined(HAVE_SHM_CREATE_LARGEPAGE)
74-
if (shared_segment_lg_index > 0) {
75-
shared_segment->shm_fd = shm_create_largepage(shared_segment_name, shared_segment_flags, shared_segment_lg_index, SHM_LARGEPAGE_ALLOC_DEFAULT, shared_segment_mode);
76-
if (shared_segment->shm_fd != -1) {
77-
goto truncate_segment;
111+
if (shared_segment_lg_index > 0) {
112+
shared_segment_fd = shm_create_largepage(shared_segment_name, shared_segment_flags, shared_segment_lg_index, SHM_LARGEPAGE_ALLOC_DEFAULT, shared_segment_mode);
113+
if (shared_segment_fd != -1) {
114+
shared_segment->shm_fd = shared_segment_fd;
115+
116+
goto truncate_segment;
117+
}
78118
}
79-
}
80119
#endif /* HAVE_SHM_CREATE_LARGEPAGE */
81120

82-
shared_segment->shm_fd = shm_open(shared_segment_name, shared_segment_flags, shared_segment_mode);
83-
if (shared_segment->shm_fd == -1) {
121+
shared_segment_fd = shm_open(shared_segment_name, shared_segment_flags, shared_segment_mode);
122+
if (shared_segment_fd != -1) {
123+
break;
124+
}
125+
126+
if (errno != EEXIST) {
127+
*error_in = "shm_open";
128+
129+
return PHP_USER_CACHE_ALLOC_FAILURE;
130+
}
131+
}
132+
133+
if (shared_segment_fd == -1) {
84134
*error_in = "shm_open";
85135

86136
return PHP_USER_CACHE_ALLOC_FAILURE;
87137
}
88138

139+
shared_segment->shm_fd = shared_segment_fd;
140+
89141
#if defined(HAVE_SHM_CREATE_LARGEPAGE)
90142
truncate_segment:
91143
#endif /* HAVE_SHM_CREATE_LARGEPAGE */
92144
if (ftruncate(shared_segment->shm_fd, requested_size) != 0) {
93145
*error_in = "ftruncate";
94146

95147
close(shared_segment->shm_fd);
148+
96149
shm_unlink(shared_segment_name);
97150

98151
return PHP_USER_CACHE_ALLOC_FAILURE;
@@ -103,12 +156,23 @@ static int user_cache_alloc_posix_create_segments(size_t requested_size, php_use
103156
*error_in = "mmap";
104157

105158
close(shared_segment->shm_fd);
159+
106160
shm_unlink(shared_segment_name);
107161

108162
return PHP_USER_CACHE_ALLOC_FAILURE;
109163
}
110164

111-
shm_unlink(shared_segment_name);
165+
if (shm_unlink(shared_segment_name) != 0) {
166+
*error_in = "shm_unlink";
167+
168+
munmap(shared_segment->common.p, requested_size);
169+
170+
shared_segment->common.p = NULL;
171+
172+
close(shared_segment->shm_fd);
173+
174+
return PHP_USER_CACHE_ALLOC_FAILURE;
175+
}
112176

113177
shared_segment->common.size = requested_size;
114178

0 commit comments

Comments
 (0)