Skip to content

Commit 517b553

Browse files
committed
Fixed bug #70630 (Closure::call/bind() crash with ReflectionFunction->getClosure())
This additionally removes support for binding to an unknown (not in parent hierarchy) scope. Removing support for cross-scope is necessary for certain compile-time assumptions (like class constants) to prevent unexpected results
1 parent 4cb6342 commit 517b553

File tree

6 files changed

+124
-16
lines changed

6 files changed

+124
-16
lines changed

NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
15 Oct 2015, PHP 7.0.0 RC 5
44

5+
- Core
6+
. Fixed bug #70630 (Closure::call/bind() crash with
7+
ReflectionFunction->getClosure()). (Bob)
8+
59
- Mcrypt:
610
. Fixed bug #70625 (mcrypt_encrypt() won't return data when no IV was
711
specified under RC4). (Nikita)

Zend/tests/bug70630.phpt

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Bug #70630 (Closure::call/bind() crash with ReflectionFunction->getClosure())
3+
--FILE--
4+
<?php
5+
6+
class a {}
7+
function foo() {}
8+
9+
foreach (["substr", "foo"] as $fn) {
10+
$x = (new ReflectionFunction($fn))->getClosure();
11+
$x->call(new a);
12+
Closure::bind($x, new a, "a");
13+
}
14+
15+
?>
16+
--EXPECTF--
17+
18+
Warning: Cannot bind function substr to an object in %s on line %d
19+
20+
Warning: Cannot bind function substr to an object in %s on line %d
21+
22+
Warning: Cannot bind function foo to an object in %s on line %d
23+
24+
Warning: Cannot bind function foo to an object in %s on line %d

Zend/tests/closure_061.phpt

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
--TEST--
2+
Closure::call() or Closure::bind() to independent class must fail
3+
--FILE--
4+
<?php
5+
6+
class foo {
7+
public $var;
8+
9+
function initClass() {
10+
$this->var = __CLASS__;
11+
}
12+
}
13+
14+
class bar {
15+
public $var;
16+
17+
function initClass() {
18+
$this->var = __CLASS__;
19+
}
20+
21+
function getVar() {
22+
assert($this->var !== null); // ensure initClass was called
23+
return $this->var;
24+
}
25+
}
26+
27+
class baz extends bar {
28+
public $var;
29+
30+
function initClass() {
31+
$this->var = __CLASS__;
32+
}
33+
}
34+
35+
function callMethodOn($class, $method, $object) {
36+
$closure = (new ReflectionMethod($class, $method))->getClosure((new ReflectionClass($class))->newInstanceWithoutConstructor());
37+
$closure = $closure->bindTo($object, $class);
38+
return $closure();
39+
}
40+
41+
$baz = new baz;
42+
43+
callMethodOn("baz", "initClass", $baz);
44+
var_dump($baz->getVar());
45+
46+
callMethodOn("bar", "initClass", $baz);
47+
var_dump($baz->getVar());
48+
49+
callMethodOn("foo", "initClass", $baz);
50+
var_dump($baz->getVar());
51+
52+
?>
53+
--EXPECTF--
54+
string(3) "baz"
55+
string(3) "bar"
56+
57+
Warning: Cannot bind function foo::initClass to object of class baz in %s on line %d
58+
59+
Fatal error: Uncaught Error: Using $this when not in object context in %s:%d
60+
Stack trace:
61+
#0 %s(%d): initClass()
62+
#1 %s(%d): callMethodOn('foo', 'initClass', Object(baz))
63+
#2 {main}
64+
thrown in %s on line %d

Zend/zend_closures.c

+30-14
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,12 @@ ZEND_METHOD(Closure, call)
9494
return;
9595
}
9696

97-
if (closure->func.type == ZEND_INTERNAL_FUNCTION) {
98-
/* verify that we aren't binding internal function to a wrong object */
99-
if ((closure->func.common.fn_flags & ZEND_ACC_STATIC) == 0 &&
100-
!instanceof_function(Z_OBJCE_P(newthis), closure->func.common.scope)) {
97+
if (closure->func.type != ZEND_USER_FUNCTION || (closure->func.common.fn_flags & ZEND_ACC_REAL_CLOSURE) == 0) {
98+
/* verify that we aren't binding methods to a wrong object */
99+
if (closure->func.common.scope == NULL) {
100+
zend_error(E_WARNING, "Cannot bind function %s to an object", ZSTR_VAL(closure->func.common.function_name));
101+
return;
102+
} else if (!instanceof_function(Z_OBJCE_P(newthis), closure->func.common.scope)) {
101103
zend_error(E_WARNING, "Cannot bind function %s::%s to object of class %s", ZSTR_VAL(closure->func.common.scope->name), ZSTR_VAL(closure->func.common.function_name), ZSTR_VAL(Z_OBJCE_P(newthis)->name));
102104
return;
103105
}
@@ -165,7 +167,7 @@ ZEND_METHOD(Closure, bind)
165167
RETURN_NULL();
166168
}
167169

168-
closure = (zend_closure *)Z_OBJ_P(zclosure);
170+
closure = (zend_closure *) Z_OBJ_P(zclosure);
169171

170172
if ((newthis != NULL) && (closure->func.common.fn_flags & ZEND_ACC_STATIC)) {
171173
zend_error(E_WARNING, "Cannot bind an instance to a static closure");
@@ -187,7 +189,7 @@ ZEND_METHOD(Closure, bind)
187189
}
188190
zend_string_release(class_name);
189191
}
190-
if(ce && ce != closure->func.common.scope && ce->type == ZEND_INTERNAL_CLASS) {
192+
if (ce && ce != closure->func.common.scope && ce->type == ZEND_INTERNAL_CLASS) {
191193
/* rebinding to internal class is not allowed */
192194
zend_error(E_WARNING, "Cannot bind closure to scope of internal class %s", ZSTR_VAL(ce->name));
193195
return;
@@ -202,6 +204,22 @@ ZEND_METHOD(Closure, bind)
202204
called_scope = ce;
203205
}
204206

207+
/* verify that we aren't binding methods to a wrong object */
208+
if (closure->func.type != ZEND_USER_FUNCTION || (closure->func.common.fn_flags & ZEND_ACC_REAL_CLOSURE) == 0) {
209+
if (!closure->func.common.scope) {
210+
if (ce) {
211+
zend_error(E_WARNING, "Cannot bind function %s to an object", ZSTR_VAL(closure->func.common.function_name));
212+
return;
213+
}
214+
} else if (!ce) {
215+
zend_error(E_WARNING, "Cannot bind function %s::%s to no class", ZSTR_VAL(closure->func.common.scope->name), ZSTR_VAL(closure->func.common.function_name));
216+
return;
217+
} else if (!instanceof_function(ce, closure->func.common.scope)) {
218+
zend_error(E_WARNING, "Cannot bind function %s::%s to class %s", ZSTR_VAL(closure->func.common.scope->name), ZSTR_VAL(closure->func.common.function_name), ZSTR_VAL(ce->name));
219+
return;
220+
}
221+
}
222+
205223
zend_create_closure(return_value, &closure->func, ce, called_scope, newthis);
206224
new_closure = (zend_closure *) Z_OBJ_P(return_value);
207225

@@ -242,11 +260,9 @@ ZEND_API zend_function *zend_get_closure_invoke_method(zend_object *object) /* {
242260
* and we won't check arguments on internal function. We also set
243261
* ZEND_ACC_USER_ARG_INFO flag to prevent invalid usage by Reflection */
244262
invoke->type = ZEND_INTERNAL_FUNCTION;
245-
invoke->internal_function.fn_flags =
246-
ZEND_ACC_PUBLIC | ZEND_ACC_CALL_VIA_HANDLER | (closure->func.common.fn_flags & keep_flags);
263+
invoke->internal_function.fn_flags = ZEND_ACC_PUBLIC | ZEND_ACC_CALL_VIA_HANDLER | (closure->func.common.fn_flags & keep_flags);
247264
if (closure->func.type != ZEND_INTERNAL_FUNCTION || (closure->func.common.fn_flags & ZEND_ACC_USER_ARG_INFO)) {
248-
invoke->internal_function.fn_flags |=
249-
ZEND_ACC_USER_ARG_INFO;
265+
invoke->internal_function.fn_flags |= ZEND_ACC_USER_ARG_INFO;
250266
}
251267
invoke->internal_function.handler = ZEND_MN(Closure___invoke);
252268
invoke->internal_function.module = 0;
@@ -523,10 +539,10 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
523539

524540
object_init_ex(res, zend_ce_closure);
525541

526-
closure = (zend_closure *)Z_OBJ_P(res);
542+
closure = (zend_closure *) Z_OBJ_P(res);
527543

528544
memcpy(&closure->func, func, func->type == ZEND_USER_FUNCTION ? sizeof(zend_op_array) : sizeof(zend_internal_function));
529-
closure->func.common.prototype = (zend_function*)closure;
545+
closure->func.common.prototype = (zend_function *) closure;
530546
closure->func.common.fn_flags |= ZEND_ACC_CLOSURE;
531547

532548
if ((scope == NULL) && this_ptr && (Z_TYPE_P(this_ptr) != IS_UNDEF)) {
@@ -550,7 +566,8 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
550566
if (closure->func.op_array.refcount) {
551567
(*closure->func.op_array.refcount)++;
552568
}
553-
} else {
569+
}
570+
if (closure->func.type != ZEND_USER_FUNCTION || (func->common.fn_flags & ZEND_ACC_REAL_CLOSURE) == 0) {
554571
/* verify that we aren't binding internal function to a wrong scope */
555572
if(func->common.scope != NULL) {
556573
if(scope && !instanceof_function(scope, func->common.scope)) {
@@ -561,7 +578,6 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
561578
!instanceof_function(Z_OBJCE_P(this_ptr), closure->func.common.scope)) {
562579
zend_error(E_WARNING, "Cannot bind function %s::%s to object of class %s", ZSTR_VAL(func->common.scope->name), ZSTR_VAL(func->common.function_name), ZSTR_VAL(Z_OBJCE_P(this_ptr)->name));
563580
scope = NULL;
564-
this_ptr = NULL;
565581
}
566582
} else {
567583
/* if it's a free function, we won't set scope & this since they're meaningless */

Zend/zend_compile.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -4854,7 +4854,7 @@ void zend_compile_func_decl(znode *result, zend_ast *ast) /* {{{ */
48544854
op_array->doc_comment = zend_string_copy(decl->doc_comment);
48554855
}
48564856
if (decl->kind == ZEND_AST_CLOSURE) {
4857-
op_array->fn_flags |= ZEND_ACC_CLOSURE;
4857+
op_array->fn_flags |= ZEND_ACC_CLOSURE | ZEND_ACC_REAL_CLOSURE;
48584858
}
48594859

48604860
if (is_method) {

Zend/zend_compile.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ typedef struct _zend_try_catch_element {
238238
/* user class has methods with static variables */
239239
#define ZEND_HAS_STATIC_IN_METHODS 0x800000
240240

241-
241+
#define ZEND_ACC_REAL_CLOSURE 0x40
242242
#define ZEND_ACC_CLOSURE 0x100000
243243
#define ZEND_ACC_GENERATOR 0x800000
244244

0 commit comments

Comments
 (0)