Skip to content

Commit 4f70a41

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 22194b3 commit 4f70a41

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
@@ -3927,6 +3927,25 @@ static zend_always_inline bool zend_is_callable_check_func(const zval *callable,
39273927
goto get_function_via_handler;
39283928
}
39293929
}
3930+
3931+
/* Check namespace visibility */
3932+
if (fcc->function_handler && UNEXPECTED(fcc->function_handler->common.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) {
3933+
zend_string *method_namespace = zend_get_class_namespace(fcc->function_handler->common.scope);
3934+
zend_string *caller_namespace = zend_get_caller_namespace();
3935+
3936+
if (!zend_string_equals(method_namespace, caller_namespace)) {
3937+
if (fcc->calling_scope &&
3938+
((fcc->object && fcc->calling_scope->__call) ||
3939+
(!fcc->object && fcc->calling_scope->__callstatic))) {
3940+
retval = false;
3941+
fcc->function_handler = NULL;
3942+
goto get_function_via_handler;
3943+
} else {
3944+
retval = false;
3945+
fcc->function_handler = NULL;
3946+
}
3947+
}
3948+
}
39303949
} else {
39313950
get_function_via_handler:
39323951
if (fcc->object && fcc->calling_scope == ce_org) {
@@ -3994,6 +4013,22 @@ static zend_always_inline bool zend_is_callable_check_func(const zval *callable,
39944013
retval = false;
39954014
}
39964015
}
4016+
4017+
/* Check namespace visibility */
4018+
if (retval && fcc->function_handler && UNEXPECTED(fcc->function_handler->common.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) {
4019+
zend_string *method_namespace = zend_get_class_namespace(fcc->function_handler->common.scope);
4020+
zend_string *caller_namespace = zend_get_caller_namespace();
4021+
4022+
if (!zend_string_equals(method_namespace, caller_namespace)) {
4023+
if (error) {
4024+
if (*error) {
4025+
efree(*error);
4026+
}
4027+
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));
4028+
}
4029+
retval = false;
4030+
}
4031+
}
39974032
}
39984033
} else if (error) {
39994034
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)