Skip to content

Commit 19d5bd3

Browse files
committed
[sysvshm] Clamp shm_get_var chunk length to segment bounds
shm_get_var() trusted the sysvshm_chunk->length field read straight from the shared memory segment and handed it to php_var_unserialize() as the end bound: a hostile segment claiming a multi-gigabyte length made the parser read past ptr->end and past the end of the SysV mapping. Reject a negative or out-of-bounds length with the existing corruption warning and harden php_check_shm_data() so a chain walk never dereferences a chunk header that does not fully fit before ptr->end. Sibling audit: php_remove_shm_data() only memmoves when memcpy_len > 0, which bounds it to [shm_varpos, old end), and sysvmsg unserializes exactly msgrcv()'s kernel-bounded byte count, so both are unaffected.
1 parent 7873640 commit 19d5bd3

2 files changed

Lines changed: 81 additions & 2 deletions

File tree

ext/sysvshm/sysvshm.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ PHP_FUNCTION(shm_get_var)
309309
sysvshm_shm *shm_list_ptr;
310310
char *shm_data;
311311
zend_long shm_varpos;
312+
zend_long shm_avail;
313+
zend_long shm_len;
312314
sysvshm_chunk *shm_var;
313315
php_unserialize_data_t var_hash;
314316

@@ -331,10 +333,16 @@ PHP_FUNCTION(shm_get_var)
331333
RETURN_FALSE;
332334
}
333335
shm_var = (sysvshm_chunk*) ((char *)shm_list_ptr->ptr + shm_varpos);
336+
shm_avail = shm_list_ptr->ptr->end - shm_varpos - (zend_long) sizeof(sysvshm_chunk);
337+
if (shm_var->length < 0 || shm_var->length > shm_avail) {
338+
php_error_docref(NULL, E_WARNING, "Variable data in shared memory is corrupted");
339+
RETURN_FALSE;
340+
}
341+
shm_len = shm_var->length;
334342
shm_data = &shm_var->mem;
335343

336344
PHP_VAR_UNSERIALIZE_INIT(var_hash);
337-
int res = php_var_unserialize(return_value, (const unsigned char **) &shm_data, (unsigned char *) shm_data + shm_var->length, &var_hash);
345+
int res = php_var_unserialize(return_value, (const unsigned char **) &shm_data, (unsigned char *) shm_data + shm_len, &var_hash);
338346
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
339347
if (res != 1) {
340348
php_error_docref(NULL, E_WARNING, "Variable data in shared memory is corrupted");
@@ -433,7 +441,7 @@ static zend_long php_check_shm_data(sysvshm_chunk_head *ptr, zend_long key)
433441
pos = ptr->start;
434442

435443
for (;;) {
436-
if (pos >= ptr->end) {
444+
if (ptr->end - pos < (zend_long) sizeof(sysvshm_chunk)) {
437445
return -1;
438446
}
439447
shm_var = (sysvshm_chunk*) ((char *) ptr + pos);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
--TEST--
2+
shm_get_var() must not trust the chunk length stored in a hostile segment for unserialize()
3+
--EXTENSIONS--
4+
sysvshm
5+
ffi
6+
--FILE--
7+
<?php
8+
9+
function craft_hostile_segment(int $key, int $len): void {
10+
$ffi = FFI::cdef("
11+
typedef struct { char magic[6]; long start; long end; long free_; long total; } head_t;
12+
typedef struct { long key; long length; long next; char mem[16]; } chunk_t;
13+
int shmget(int, int, int);
14+
void *shmat(int, const void *, int);
15+
");
16+
$id = $ffi->shmget($key, 4096, 0666 | 01000);
17+
if ($id < 0) {
18+
echo "shm setup failed\n";
19+
return;
20+
}
21+
$p = $ffi->shmat($id, NULL, 0);
22+
if ($p == $ffi->cast('char*', -1)) {
23+
echo "shmat failed\n";
24+
return;
25+
}
26+
FFI::memset($p, 0, 4096);
27+
$head = $ffi->cast('head_t*', $p);
28+
FFI::memcpy($head->magic, "PHP_SM", 6);
29+
$head->start = 40;
30+
$head->end = 4096;
31+
$head->free_ = 0;
32+
$head->total = 4096;
33+
$chunk = $ffi->cast('chunk_t*', $ffi->cast('char*', $p) + 40);
34+
$chunk->key = 1;
35+
$chunk->length = $len;
36+
$chunk->next = 4096 - 40;
37+
FFI::memcpy($chunk->mem, "i:42;", 5);
38+
}
39+
40+
$key1 = 0x5A5A0E01;
41+
$key2 = 0x5A5A0E02;
42+
43+
$old = @shm_attach($key1);
44+
if ($old !== false) {
45+
shm_remove($old);
46+
}
47+
$old = @shm_attach($key2);
48+
if ($old !== false) {
49+
shm_remove($old);
50+
}
51+
craft_hostile_segment($key1, PHP_INT_MAX);
52+
53+
$shm = shm_attach($key1, 4096);
54+
var_dump(shm_has_var($shm, 1));
55+
var_dump(shm_get_var($shm, 1));
56+
shm_remove($shm);
57+
58+
$shm2 = shm_attach($key2, 4096);
59+
shm_put_var($shm2, 1, 42);
60+
var_dump(shm_get_var($shm2, 1));
61+
shm_remove($shm2);
62+
63+
echo "Done\n";
64+
?>
65+
--EXPECTF--
66+
bool(true)
67+
68+
Warning: shm_get_var(): Variable data in shared memory is corrupted in %s on line %d
69+
bool(false)
70+
int(42)
71+
Done

0 commit comments

Comments
 (0)