Skip to content

Commit b110152

Browse files
committed
Enforce namespace-scoped visibility for method calls
Add runtime checks to enforce private(namespace) visibility on methods. When a method with ZEND_ACC_NAMESPACE_PRIVATE is called, the engine now: 1. Gets the namespace where the method was declared (from class name) 2. Gets the namespace of the calling code (zend_get_caller_namespace) 3. Compares namespaces: must match exactly 4. Throws error if namespaces don't match This applies to: * Instance methods (zend_std_get_method) * Static methods (zend_std_get_static_method) * Constructors (zend_std_get_constructor) * Callable verification (zend_is_callable_at_frame)
1 parent b726cd6 commit b110152

File tree

2 files changed

+79
-1
lines changed

2 files changed

+79
-1
lines changed

Zend/zend_API.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3950,6 +3950,25 @@ static zend_always_inline bool zend_is_callable_check_func(zval *callable, const
39503950
goto get_function_via_handler;
39513951
}
39523952
}
3953+
3954+
/* Check namespace visibility */
3955+
if (fcc->function_handler && UNEXPECTED(fcc->function_handler->common.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) {
3956+
zend_string *method_namespace = zend_get_class_namespace(fcc->function_handler->common.scope);
3957+
zend_string *caller_namespace = zend_get_caller_namespace();
3958+
3959+
if (!zend_string_equals(method_namespace, caller_namespace)) {
3960+
if (fcc->calling_scope &&
3961+
((fcc->object && fcc->calling_scope->__call) ||
3962+
(!fcc->object && fcc->calling_scope->__callstatic))) {
3963+
retval = false;
3964+
fcc->function_handler = NULL;
3965+
goto get_function_via_handler;
3966+
} else {
3967+
retval = false;
3968+
fcc->function_handler = NULL;
3969+
}
3970+
}
3971+
}
39533972
} else {
39543973
get_function_via_handler:
39553974
if (fcc->object && fcc->calling_scope == ce_org) {
@@ -4017,6 +4036,22 @@ static zend_always_inline bool zend_is_callable_check_func(zval *callable, const
40174036
retval = false;
40184037
}
40194038
}
4039+
4040+
/* Check namespace visibility */
4041+
if (retval && fcc->function_handler && UNEXPECTED(fcc->function_handler->common.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) {
4042+
zend_string *method_namespace = zend_get_class_namespace(fcc->function_handler->common.scope);
4043+
zend_string *caller_namespace = zend_get_caller_namespace();
4044+
4045+
if (!zend_string_equals(method_namespace, caller_namespace)) {
4046+
if (error) {
4047+
if (*error) {
4048+
efree(*error);
4049+
}
4050+
zend_spprintf(error, 0, "cannot access %s method %s::%s()", zend_visibility_string(fcc->function_handler->common.fn_flags), ZSTR_VAL(fcc->calling_scope->name), ZSTR_VAL(fcc->function_handler->common.function_name));
4051+
}
4052+
retval = false;
4053+
}
4054+
}
40204055
}
40214056
} else if (error) {
40224057
if (fcc->calling_scope) {

Zend/zend_object_handlers.c

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1871,7 +1871,7 @@ ZEND_API zend_function *zend_std_get_method(zend_object **obj_ptr, zend_string *
18711871
fbc = Z_FUNC_P(func);
18721872

18731873
/* Check access level */
1874-
if (fbc->op_array.fn_flags & (ZEND_ACC_CHANGED|ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED)) {
1874+
if (fbc->op_array.fn_flags & (ZEND_ACC_CHANGED|ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED|ZEND_ACC_NAMESPACE_PRIVATE)) {
18751875
const zend_class_entry *scope = zend_get_executed_scope();
18761876

18771877
if (fbc->common.scope != scope) {
@@ -1895,6 +1895,21 @@ ZEND_API zend_function *zend_std_get_method(zend_object **obj_ptr, zend_string *
18951895
}
18961896
}
18971897
}
1898+
1899+
/* Check namespace visibility */
1900+
if (fbc && UNEXPECTED(fbc->op_array.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) {
1901+
zend_string *method_namespace = zend_get_class_namespace(fbc->common.scope);
1902+
zend_string *caller_namespace = zend_get_caller_namespace();
1903+
1904+
if (!zend_string_equals(method_namespace, caller_namespace)) {
1905+
if (zobj->ce->__call) {
1906+
fbc = zend_get_call_trampoline_func(zobj->ce->__call, method_name);
1907+
} else {
1908+
zend_bad_method_call(fbc, method_name, scope);
1909+
fbc = NULL;
1910+
}
1911+
}
1912+
}
18981913
}
18991914

19001915
exit:
@@ -1952,6 +1967,21 @@ ZEND_API zend_function *zend_std_get_static_method(const zend_class_entry *ce, z
19521967
fbc = fallback_fbc;
19531968
}
19541969
}
1970+
1971+
/* Check namespace visibility */
1972+
if (fbc && UNEXPECTED(fbc->common.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) {
1973+
zend_string *method_namespace = zend_get_class_namespace(fbc->common.scope);
1974+
zend_string *caller_namespace = zend_get_caller_namespace();
1975+
1976+
if (!zend_string_equals(method_namespace, caller_namespace)) {
1977+
const zend_class_entry *scope = zend_get_executed_scope();
1978+
zend_function *fallback_fbc = get_static_method_fallback(ce, function_name);
1979+
if (!fallback_fbc) {
1980+
zend_bad_method_call(fbc, function_name, scope);
1981+
}
1982+
fbc = fallback_fbc;
1983+
}
1984+
}
19551985
} else {
19561986
fbc = get_static_method_fallback(ce, function_name);
19571987
}
@@ -2113,6 +2143,19 @@ ZEND_API zend_function *zend_std_get_constructor(zend_object *zobj) /* {{{ */
21132143
constructor = NULL;
21142144
}
21152145
}
2146+
2147+
/* Check namespace visibility */
2148+
if (constructor && UNEXPECTED(constructor->common.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) {
2149+
zend_string *method_namespace = zend_get_class_namespace(constructor->common.scope);
2150+
zend_string *caller_namespace = zend_get_caller_namespace();
2151+
2152+
if (!zend_string_equals(method_namespace, caller_namespace)) {
2153+
const zend_class_entry *scope = get_fake_or_executed_scope();
2154+
zend_bad_constructor_call(constructor, scope);
2155+
zend_object_store_ctor_failed(zobj);
2156+
constructor = NULL;
2157+
}
2158+
}
21162159
}
21172160

21182161
return constructor;

0 commit comments

Comments
 (0)