Skip to content

Commit 4e0bd03

Browse files
committed
Reset EG(trampoline).op_array.last_var that FFI may modify
Closes GH-10916
1 parent 21e0305 commit 4e0bd03

File tree

3 files changed

+50
-0
lines changed

3 files changed

+50
-0
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ PHP NEWS
1515
(nielsdos)
1616
. Fixed bug GH-10810 (Fix NUL byte terminating Exception::__toString()).
1717
(ilutov)
18+
. Fix potential memory corruption when mixing __callStatic() and FFI. (ilutov)
1819

1920
- Date:
2021
. Fixed bug GH-10583 (DateTime modify with tz pattern should not update

Zend/zend_object_handlers.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,12 @@ ZEND_API zend_function *zend_get_call_trampoline_func(zend_class_entry *ce, zend
12531253
ZEND_MAP_PTR_INIT(func->run_time_cache, (void***)&dummy);
12541254
func->scope = fbc->common.scope;
12551255
/* reserve space for arguments, local and temporary variables */
1256+
/* EG(trampoline) is reused from other places, like FFI (e.g. zend_ffi_cdata_get_closure()) where
1257+
* it is used as an internal function. It may set fields that don't belong to common, thus
1258+
* modifying zend_op_array specific data, most significantly last_var. We need to reset this
1259+
* value so that it doesn't contain garbage when the engine allocates space for the next stack
1260+
* frame. This didn't cause any issues until now due to "lucky" structure layout. */
1261+
func->last_var = 0;
12561262
func->T = (fbc->type == ZEND_USER_FUNCTION)? MAX(fbc->op_array.last_var + fbc->op_array.T, 2) : 2;
12571263
func->filename = (fbc->type == ZEND_USER_FUNCTION)? fbc->op_array.filename : ZSTR_EMPTY_ALLOC();
12581264
func->line_start = (fbc->type == ZEND_USER_FUNCTION)? fbc->op_array.line_start : 0;

ext/ffi/tests/trampoline_reset.phpt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
--TEST--
2+
Memory corruption when mixing __callStatic() and FFI
3+
--EXTENSIONS--
4+
ffi
5+
--SKIPIF--
6+
<?php
7+
try {
8+
$libc = FFI::cdef("int printf(const char *format, ...);", "libc.so.6");
9+
} catch (Throwable $_) {
10+
die('skip libc.so.6 not available');
11+
}
12+
?>
13+
--INI--
14+
ffi.enable=1
15+
--FILE--
16+
<?php
17+
class Test
18+
{
19+
public static function __callStatic($name, $args)
20+
{
21+
echo "$name called\n";
22+
}
23+
}
24+
25+
$header = '
26+
typedef struct _IO_FILE FILE;
27+
extern FILE *stdout;
28+
int fprintf(FILE *, const char *, ...);
29+
int fflush(FILE *);
30+
';
31+
$ffi = FFI::cdef($header, 'libc.so.6');
32+
33+
Test::foo();
34+
Test::bar();
35+
$ffi->fprintf($ffi->stdout, "FFI\n");
36+
$ffi->fflush($ffi->stdout);
37+
Test::baz();
38+
?>
39+
--EXPECT--
40+
foo called
41+
bar called
42+
FFI
43+
baz called

0 commit comments

Comments
 (0)