Skip to content

Commit f90323c

Browse files
authored
Fix GH-17280: ldap_search() fails when $attributes array has holes (#17284)
We relax the constraint that the array must be a list. What really matters is that it only has numeric keys. As shown in the example code, it's really easy to accidentally create a non-list array, so it makes sense to relax the constraint. There are 3 cases left where the array is checked to be a list, in php_ldap_do_search, but I believe this makes sense to keep because the indices of those arrays have a meaning because they should match between different arrays. In that case it will prevent programmer errors.
1 parent accf957 commit f90323c

6 files changed

+55
-33
lines changed

ext/ldap/ldap.c

+30-12
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,22 @@ static void ldap_result_entry_free_obj(zend_object *obj)
234234
} \
235235
}
236236

237+
static bool php_ldap_is_numerically_indexed_array(const zend_array *arr)
238+
{
239+
if (zend_hash_num_elements(arr) == 0 || HT_IS_PACKED(arr)) {
240+
return true;
241+
}
242+
243+
zend_string *str_key;
244+
ZEND_HASH_MAP_FOREACH_STR_KEY(arr, str_key) {
245+
if (str_key) {
246+
return false;
247+
}
248+
} ZEND_HASH_FOREACH_END();
249+
250+
return true;
251+
}
252+
237253
/* An LDAP value must be a string, however it defines a format for integer and
238254
* booleans, thus we parse zvals to the corresponding string if possible
239255
* See RFC 4517: https://datatracker.ietf.org/doc/html/rfc4517 */
@@ -1481,16 +1497,16 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope)
14811497
/* We don't allocate ldap_attrs for an empty array */
14821498
goto process;
14831499
}
1484-
if (!zend_array_is_list(attributes)) {
1485-
zend_argument_value_error(4, "must be a list");
1500+
if (!php_ldap_is_numerically_indexed_array(attributes)) {
1501+
zend_argument_value_error(4, "must be an array with numeric keys");
14861502
RETURN_THROWS();
14871503
}
14881504
/* Allocate +1 as we need an extra entry to NULL terminate the list */
14891505
ldap_attrs = safe_emalloc(num_attribs+1, sizeof(char *), 0);
14901506

14911507
zend_ulong attribute_index = 0;
14921508
zval *attribute_zv = NULL;
1493-
ZEND_HASH_FOREACH_NUM_KEY_VAL(attributes, attribute_index, attribute_zv) {
1509+
ZEND_HASH_FOREACH_VAL(attributes, attribute_zv) {
14941510
ZVAL_DEREF(attribute_zv);
14951511
if (Z_TYPE_P(attribute_zv) != IS_STRING) {
14961512
zend_argument_type_error(4, "must be a list of strings, %s given", zend_zval_value_name(attribute_zv));
@@ -1503,7 +1519,7 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope)
15031519
ret = 0;
15041520
goto cleanup;
15051521
}
1506-
ldap_attrs[attribute_index] = ZSTR_VAL(attribute);
1522+
ldap_attrs[attribute_index++] = ZSTR_VAL(attribute);
15071523
} ZEND_HASH_FOREACH_END();
15081524
ldap_attrs[num_attribs] = NULL;
15091525
}
@@ -2302,8 +2318,8 @@ static void php_ldap_do_modify(INTERNAL_FUNCTION_PARAMETERS, int oper, int ext)
23022318
RETVAL_FALSE;
23032319
goto cleanup;
23042320
}
2305-
if (!zend_array_is_list(Z_ARRVAL_P(attribute_values))) {
2306-
zend_argument_value_error(3, "must be a list of attribute values");
2321+
if (!php_ldap_is_numerically_indexed_array(Z_ARRVAL_P(attribute_values))) {
2322+
zend_argument_value_error(3, "must be an array of attribute values with numeric keys");
23072323
RETVAL_FALSE;
23082324
goto cleanup;
23092325
}
@@ -2314,7 +2330,7 @@ static void php_ldap_do_modify(INTERNAL_FUNCTION_PARAMETERS, int oper, int ext)
23142330

23152331
zend_ulong attribute_value_index = 0;
23162332
zval *attribute_value = NULL;
2317-
ZEND_HASH_FOREACH_NUM_KEY_VAL(Z_ARRVAL_P(attribute_values), attribute_value_index, attribute_value) {
2333+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(attribute_values), attribute_value) {
23182334
zend_string *value = php_ldap_try_get_ldap_value_from_zval(attribute_value);
23192335
if (UNEXPECTED(value == NULL)) {
23202336
RETVAL_FALSE;
@@ -2324,6 +2340,7 @@ static void php_ldap_do_modify(INTERNAL_FUNCTION_PARAMETERS, int oper, int ext)
23242340
/* The string will be free by php_ldap_zend_string_release_from_char_pointer() during cleanup */
23252341
ldap_mods[attribute_index]->mod_bvalues[attribute_value_index]->bv_val = ZSTR_VAL(value);
23262342
ldap_mods[attribute_index]->mod_bvalues[attribute_value_index]->bv_len = ZSTR_LEN(value);
2343+
attribute_value_index++;
23272344
} ZEND_HASH_FOREACH_END();
23282345
ldap_mods[attribute_index]->mod_bvalues[num_values] = NULL;
23292346
}
@@ -2594,8 +2611,8 @@ PHP_FUNCTION(ldap_modify_batch)
25942611
zend_argument_must_not_be_empty_error(3);
25952612
RETURN_THROWS();
25962613
}
2597-
if (!zend_array_is_list(modifications)) {
2598-
zend_argument_value_error(3, "must be a list");
2614+
if (!php_ldap_is_numerically_indexed_array(modifications)) {
2615+
zend_argument_value_error(3, "must be an array with numeric keys");
25992616
RETURN_THROWS();
26002617
}
26012618

@@ -2689,8 +2706,8 @@ PHP_FUNCTION(ldap_modify_batch)
26892706
zend_argument_value_error(3, "the value for option \"" LDAP_MODIFY_BATCH_VALUES "\" must not be empty");
26902707
RETURN_THROWS();
26912708
}
2692-
if (!zend_array_is_list(modification_values)) {
2693-
zend_argument_value_error(3, "the value for option \"" LDAP_MODIFY_BATCH_VALUES "\" must be a list");
2709+
if (!php_ldap_is_numerically_indexed_array(modification_values)) {
2710+
zend_argument_value_error(3, "the value for option \"" LDAP_MODIFY_BATCH_VALUES "\" must be an array with numeric keys");
26942711
RETURN_THROWS();
26952712
}
26962713
} ZEND_HASH_FOREACH_END();
@@ -2752,7 +2769,7 @@ PHP_FUNCTION(ldap_modify_batch)
27522769
/* for each value */
27532770
zend_ulong value_index = 0;
27542771
zval *modification_value_zv = NULL;
2755-
ZEND_HASH_FOREACH_NUM_KEY_VAL(Z_ARRVAL_P(modification_values), value_index, modification_value_zv) {
2772+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(modification_values), modification_value_zv) {
27562773
zend_string *modval = zval_get_string(modification_value_zv);
27572774
if (EG(exception)) {
27582775
RETVAL_FALSE;
@@ -2768,6 +2785,7 @@ PHP_FUNCTION(ldap_modify_batch)
27682785
ldap_mods[modification_index]->mod_bvalues[value_index]->bv_len = ZSTR_LEN(modval);
27692786
ldap_mods[modification_index]->mod_bvalues[value_index]->bv_val = estrndup(ZSTR_VAL(modval), ZSTR_LEN(modval));
27702787
zend_string_release(modval);
2788+
value_index++;
27712789
} ZEND_HASH_FOREACH_END();
27722790

27732791
/* NULL-terminate values */

ext/ldap/tests/gh17280.phpt

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
GH-17280 (ldap_search() fails when $attributes array has holes)
3+
--EXTENSIONS--
4+
ldap
5+
--FILE--
6+
<?php
7+
8+
/* We are assuming 3333 is not connectable */
9+
$ldap = ldap_connect('ldap://127.0.0.1:3333');
10+
11+
// Creating an array with a hole in it
12+
$attr = array_unique(['cn', 'uid', 'uid', 'mail']);
13+
var_dump(ldap_search($ldap, 'ou=people,dc=example,dc=com', 'uid=admin', $attr));
14+
15+
?>
16+
--EXPECTF--
17+
Warning: ldap_search(): Search: Can't contact LDAP server in %s on line %d
18+
bool(false)

ext/ldap/tests/ldap_add_modify_delete_programming_errors.phpt

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,6 @@ ValueError: ldap_add(): Argument #3 ($entry) key must not be empty
138138
ValueError: ldap_add(): Argument #3 ($entry) key must not contain any null bytes
139139
Error: Object of class stdClass could not be converted to string
140140
ValueError: ldap_add(): Argument #3 ($entry) list of attribute values must not be empty
141-
ValueError: ldap_add(): Argument #3 ($entry) must be a list of attribute values
141+
ValueError: ldap_add(): Argument #3 ($entry) must be an array of attribute values with numeric keys
142142
TypeError: LDAP value must be of type string|int|bool, array given
143143
Error: Object of class stdClass could not be converted to string

ext/ldap/tests/ldap_list_read_search_programming_errors.phpt

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ try {
8080
TypeError: ldap_list(): Argument #1 ($ldap) must be of type LDAP\Connection|array, int given
8181
TypeError: ldap_list(): Argument #2 ($base) must be of type string when argument #1 ($ldap) is an LDAP\Connection instance
8282
TypeError: ldap_list(): Argument #3 ($filter) must be of type string when argument #1 ($ldap) is an LDAP\Connection instance
83-
ValueError: ldap_list(): Argument #4 ($attributes) must be a list
83+
ValueError: ldap_list(): Argument #4 ($attributes) must be an array with numeric keys
8484
TypeError: ldap_list(): Argument #4 ($attributes) must be a list of strings, int given
8585
ValueError: ldap_list(): Argument #4 ($attributes) must not contain strings with any null bytes
8686
ValueError: ldap_list(): Argument #4 ($attributes) must not contain strings with any null bytes

ext/ldap/tests/ldap_modify_batch_programming_error.phpt

+3-17
Original file line numberDiff line numberDiff line change
@@ -216,19 +216,6 @@ try {
216216
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
217217
}
218218

219-
$modification_values_not_list2 = [
220-
[
221-
"attrib" => "attrib1",
222-
"modtype" => LDAP_MODIFY_BATCH_ADD,
223-
"values" => $not_list2,
224-
],
225-
];
226-
try {
227-
var_dump(ldap_modify_batch($ldap, $valid_dn, $modification_values_not_list2));
228-
} catch (Throwable $e) {
229-
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
230-
}
231-
232219
$modification_missing_attrib_key = [
233220
[
234221
"modtype" => LDAP_MODIFY_BATCH_ADD,
@@ -257,8 +244,8 @@ try {
257244
--EXPECT--
258245
ValueError: ldap_modify_batch(): Argument #2 ($dn) must not contain any null bytes
259246
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) must not be empty
260-
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) must be a list
261-
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) must be a list
247+
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) must be an array with numeric keys
248+
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) a modification entry must only contain the keys "attrib", "modtype", and "values"
262249
TypeError: ldap_modify_batch(): Argument #3 ($modifications_info) must only contain arrays
263250
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) a modification entry must not contain the "values" option when option "modtype" is LDAP_MODIFY_BATCH_REMOVE_ALL
264251
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) a modification entry must only contain the keys "attrib", "modtype", and "values"
@@ -270,7 +257,6 @@ ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) a modificatio
270257
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) a modification entry must contain the "values" option when the "modtype" option is not LDAP_MODIFY_BATCH_REMOVE_ALL
271258
TypeError: ldap_modify_batch(): Argument #3 ($modifications_info) the value for option "values" must be of type array, string given
272259
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) the value for option "values" must not be empty
273-
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) the value for option "values" must be a list
274-
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) the value for option "values" must be a list
260+
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) the value for option "values" must be an array with numeric keys
275261
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) a modification entry must contain the "attrib" option
276262
ValueError: ldap_modify_batch(): Argument #3 ($modifications_info) a modification entry must contain the "modtype" option

ext/ldap/tests/ldap_search_error.phpt

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ $result = ldap_search($link, $dn, $filter);
2020
var_dump($result);
2121

2222
try {
23-
$result = ldap_search($link, $dn, $filter, array(1 => 'top'));
23+
$result = ldap_search($link, $dn, $filter, array('foo' => 'top'));
2424
var_dump($result);
2525
} catch (ValueError $exception) {
2626
echo $exception->getMessage() . "\n";
@@ -60,7 +60,7 @@ try {
6060
--EXPECTF--
6161
Warning: ldap_search(): Search: No such object in %s on line %d
6262
bool(false)
63-
ldap_search(): Argument #4 ($attributes) must be a list
63+
ldap_search(): Argument #4 ($attributes) must be an array with numeric keys
6464
ldap_search(): Argument #1 ($ldap) must not be empty
6565
ldap_search(): Argument #2 ($base) must be the same size as argument #1
6666
ldap_search(): Argument #3 ($filter) must be the same size as argument #1

0 commit comments

Comments
 (0)