From d354c14713488a2d8c5931e28cf33a6712a4a457 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 28 Sep 2024 16:07:22 +0100 Subject: [PATCH 1/6] ext/ldap: Fix GH-16101 (Segfaults in php_ldap_do_search() when LDAPs is not a list) Closes GH-16102 --- ext/ldap/ldap.c | 5 +++++ ext/ldap/tests/gh16101.phpt | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 ext/ldap/tests/gh16101.phpt diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index 3fea267d14ff9..35e4e4321c472 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -1480,6 +1480,11 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) ret = 0; goto cleanup; } + if (!zend_array_is_list(Z_ARRVAL_P(link))) { + zend_argument_value_error(1, "must be a list"); + ret = 0; + goto cleanup; + } if (base_dn_ht) { nbases = zend_hash_num_elements(base_dn_ht); diff --git a/ext/ldap/tests/gh16101.phpt b/ext/ldap/tests/gh16101.phpt new file mode 100644 index 0000000000000..1e9d57334378b --- /dev/null +++ b/ext/ldap/tests/gh16101.phpt @@ -0,0 +1,25 @@ +--TEST-- +Bug GH-16101: Segfault in ldap_list(), ldap_read(), and ldap_search() when LDAPs array is not a list +--EXTENSIONS-- +ldap +--FILE-- + $ldap, + "world" => $ldap, +]; +try { + var_dump(ldap_list($ldaps_dict, $valid_dn, $valid_filter)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +ValueError: ldap_list(): Argument #1 ($ldap) must be a list From 97f16d202027b467a6b90f9f38e8743c01f27f0e Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 28 Sep 2024 15:29:00 +0100 Subject: [PATCH 2/6] ext/ldap: Refactor validation of attributes array for php_ldap_do_search() --- ext/ldap/ldap.c | 58 +++++++++++------ ...p_list_read_search_programming_errors.phpt | 65 +++++++++++++++++++ ext/ldap/tests/ldap_search_error.phpt | 12 ++-- 3 files changed, 109 insertions(+), 26 deletions(-) create mode 100644 ext/ldap/tests/ldap_list_read_search_programming_errors.phpt diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index 35e4e4321c472..17f008ae53923 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -1402,7 +1402,7 @@ static void php_set_opts(LDAP *ldap, int sizelimit, int timelimit, int deref, in /* {{{ php_ldap_do_search */ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) { - zval *link, *attrs = NULL, *attr, *serverctrls = NULL; + zval *link, *attrs = NULL, *serverctrls = NULL; zend_string *base_dn_str, *filter_str; HashTable *base_dn_ht, *filter_ht; zend_long attrsonly, sizelimit, timelimit, deref; @@ -1414,7 +1414,7 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) LDAPControl **lserverctrls = NULL; int ldap_attrsonly = 0, ldap_sizelimit = -1, ldap_timelimit = -1, ldap_deref = -1; int old_ldap_sizelimit = -1, old_ldap_timelimit = -1, old_ldap_deref = -1; - int num_attribs = 0, ret = 1, i, ldap_errno, argcount = ZEND_NUM_ARGS(); + int ret = 1, ldap_errno, argcount = ZEND_NUM_ARGS(); ZEND_PARSE_PARAMETERS_START(3, 9) Z_PARAM_ZVAL(link) @@ -1444,29 +1444,45 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) case 5: ldap_attrsonly = attrsonly; ZEND_FALLTHROUGH; - case 4: - num_attribs = zend_hash_num_elements(Z_ARRVAL_P(attrs)); - ldap_attrs = safe_emalloc((num_attribs+1), sizeof(char *), 0); + default: + break; + } - for (i = 0; i "attrib2", + "attrib3", +]; +try { + var_dump(ldap_list($ldap, $valid_dn, $valid_filter, $not_list)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$not_list_of_strings = [ + "attrib1", + 42, + "attrib3", +]; +try { + var_dump(ldap_list($ldap, $valid_dn, $valid_filter, $not_list_of_strings)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$list_of_strings_with_null_byte = [ + "attrib1", + "attrib_with\0nul_byte", + "attrib3", +]; +try { + var_dump(ldap_list($ldap, $valid_dn, $valid_filter, $list_of_strings_with_null_byte)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$str = "attrib_with\0nul_byte"; + +$list_with_ref_nul_byte = [ + "attrib1", + &$str, + "attrib3", +]; +try { + var_dump(ldap_list($ldap, $valid_dn, $valid_filter, $list_with_ref_nul_byte)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +ValueError: ldap_list(): Argument #4 ($attributes) must be a list +TypeError: ldap_list(): Argument #4 ($attributes) must be a list of strings, int given +ValueError: ldap_list(): Argument #4 ($attributes) must not contain strings with any null bytes +ValueError: ldap_list(): Argument #4 ($attributes) must not contain strings with any null bytes diff --git a/ext/ldap/tests/ldap_search_error.phpt b/ext/ldap/tests/ldap_search_error.phpt index 4e775ad13d66e..b147b69766f34 100644 --- a/ext/ldap/tests/ldap_search_error.phpt +++ b/ext/ldap/tests/ldap_search_error.phpt @@ -19,8 +19,12 @@ $filter = "(dc=*)"; $result = ldap_search($link, $dn, $filter); var_dump($result); -$result = ldap_search($link, $dn, $filter, array(1 => 'top')); -var_dump($result); +try { + $result = ldap_search($link, $dn, $filter, array(1 => 'top')); + var_dump($result); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} try { ldap_search(array(), $dn, $filter, array('top')); @@ -56,9 +60,7 @@ try { --EXPECTF-- Warning: ldap_search(): Search: No such object in %s on line %d bool(false) - -Warning: ldap_search(): Array initialization wrong in %s on line %d -bool(false) +ldap_search(): Argument #4 ($attributes) must be a list ldap_search(): Argument #1 ($ldap) must not be empty ldap_search(): Argument #2 ($base) must have the same number of elements as the links array ldap_search(): Argument #3 ($filter) must have the same number of elements as the links array From cf7f38e19ef03363dd4e15a204b275e671303484 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 28 Sep 2024 16:18:52 +0100 Subject: [PATCH 3/6] ext/ldap: Improve validation of inputs for parallel search --- ext/ldap/ldap.c | 56 +++++++++------ ...ad_search_parallel_programming_errors.phpt | 68 +++++++++++++++++++ ext/ldap/tests/ldap_search_error.phpt | 4 +- 3 files changed, 105 insertions(+), 23 deletions(-) create mode 100644 ext/ldap/tests/ldap_list_read_search_parallel_programming_errors.phpt diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index 17f008ae53923..cb68737d3e3d6 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -1486,12 +1486,12 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) /* parallel search? */ if (Z_TYPE_P(link) == IS_ARRAY) { - int i, nlinks, nbases, nfilters, *rcs; + int i, *rcs; ldap_linkdata **lds; zval *entry, object; - nlinks = zend_hash_num_elements(Z_ARRVAL_P(link)); - if (nlinks == 0) { + uint32_t num_links = zend_hash_num_elements(Z_ARRVAL_P(link)); + if (num_links == 0) { zend_argument_must_not_be_empty_error(1); ret = 0; goto cleanup; @@ -1502,43 +1502,57 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) goto cleanup; } + uint32_t num_base_dns = 0; /* If 0 this means we are working with a unique base dn */ if (base_dn_ht) { - nbases = zend_hash_num_elements(base_dn_ht); - if (nbases != nlinks) { - zend_argument_value_error(2, "must have the same number of elements as the links array"); + if (!zend_array_is_list(base_dn_ht)) { + zend_argument_value_error(2, "must be a list"); + ret = 0; + goto cleanup; + } + num_base_dns = zend_hash_num_elements(base_dn_ht); + if (num_base_dns != num_links) { + zend_argument_value_error(2, "must be the same size as argument #1"); ret = 0; goto cleanup; } zend_hash_internal_pointer_reset(base_dn_ht); } else { - nbases = 0; /* this means string, not array */ - ldap_base_dn = zend_string_copy(base_dn_str); - if (EG(exception)) { + if (zend_str_has_nul_byte(base_dn_str)) { + zend_argument_value_error(2, "must not contain null bytes"); ret = 0; goto cleanup; } - // TODO check filter does not have any nul bytes + ldap_base_dn = zend_string_copy(base_dn_str); } + uint32_t num_filters = 0; /* If 0 this means we are working with a unique base dn */ if (filter_ht) { - nfilters = zend_hash_num_elements(filter_ht); - if (nfilters != nlinks) { - zend_argument_value_error(3, "must have the same number of elements as the links array"); + if (!zend_array_is_list(filter_ht)) { + zend_argument_value_error(3, "must be a list"); + ret = 0; + goto cleanup; + } + num_filters = zend_hash_num_elements(filter_ht); + if (num_filters != num_links) { + zend_argument_value_error(3, "must be the same size as argument #1"); ret = 0; goto cleanup; } zend_hash_internal_pointer_reset(filter_ht); } else { - nfilters = 0; /* this means string, not array */ + if (zend_str_has_nul_byte(filter_str)) { + zend_argument_value_error(3, "must not contain null bytes"); + ret = 0; + goto cleanup; + } ldap_filter = zend_string_copy(filter_str); - // TODO check filter does not have any nul bytes } - lds = safe_emalloc(nlinks, sizeof(ldap_linkdata), 0); - rcs = safe_emalloc(nlinks, sizeof(*rcs), 0); + lds = safe_emalloc(num_links, sizeof(ldap_linkdata), 0); + rcs = safe_emalloc(num_links, sizeof(*rcs), 0); zend_hash_internal_pointer_reset(Z_ARRVAL_P(link)); - for (i=0; ilink, LDAP_RES_ANY, 1 /* LDAP_MSG_ALL */, NULL, &ldap_res); } diff --git a/ext/ldap/tests/ldap_list_read_search_parallel_programming_errors.phpt b/ext/ldap/tests/ldap_list_read_search_parallel_programming_errors.phpt new file mode 100644 index 0000000000000..779a9fd98c59c --- /dev/null +++ b/ext/ldap/tests/ldap_list_read_search_parallel_programming_errors.phpt @@ -0,0 +1,68 @@ +--TEST-- +Programming errors (Value/Type errors) for parallel usage of ldap_list(), ldap_read(), and ldap_search() +--EXTENSIONS-- +ldap +--FILE-- +getMessage(), PHP_EOL; +} + +$filter = "filter_with\0nul_byte"; +try { + var_dump(ldap_list($ldaps, $valid_dn, $filter)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$not_list = [ + "attrib1", + "wat" => "attrib2", +]; +try { + var_dump(ldap_list($ldaps, $not_list, $valid_filter)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump(ldap_list($ldaps, $valid_dn, $not_list)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$list_not_same_length = [ + "string1", + "string2", + "string3", +]; +try { + var_dump(ldap_list($ldaps, $list_not_same_length, $valid_filter)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump(ldap_list($ldaps, $valid_dn, $list_not_same_length)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +ValueError: ldap_list(): Argument #2 ($base) must not contain null bytes +ValueError: ldap_list(): Argument #3 ($filter) must not contain null bytes +ValueError: ldap_list(): Argument #2 ($base) must be a list +ValueError: ldap_list(): Argument #3 ($filter) must be a list +ValueError: ldap_list(): Argument #2 ($base) must be the same size as argument #1 +ValueError: ldap_list(): Argument #3 ($filter) must be the same size as argument #1 diff --git a/ext/ldap/tests/ldap_search_error.phpt b/ext/ldap/tests/ldap_search_error.phpt index b147b69766f34..13613ba9d5c35 100644 --- a/ext/ldap/tests/ldap_search_error.phpt +++ b/ext/ldap/tests/ldap_search_error.phpt @@ -62,7 +62,7 @@ Warning: ldap_search(): Search: No such object in %s on line %d bool(false) ldap_search(): Argument #4 ($attributes) must be a list ldap_search(): Argument #1 ($ldap) must not be empty -ldap_search(): Argument #2 ($base) must have the same number of elements as the links array -ldap_search(): Argument #3 ($filter) must have the same number of elements as the links array +ldap_search(): Argument #2 ($base) must be the same size as argument #1 +ldap_search(): Argument #3 ($filter) must be the same size as argument #1 ldap_search(): Argument #2 ($base) must be of type string when argument #1 ($ldap) is an LDAP instance ldap_search(): Argument #3 ($filter) must be of type string when argument #1 ($ldap) is an LDAP instance From ca18fec277165b1ae53953702eec2ce4b4d4ed93 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 28 Sep 2024 17:06:27 +0100 Subject: [PATCH 4/6] ext/ldap: Use HASH_FOREACH macro to traverse array --- ext/ldap/ldap.c | 75 +++++++++++-------- ...ad_search_parallel_programming_errors.phpt | 45 +++++++++++ ..._list_read_search_parallel_references.phpt | 37 +++++++++ 3 files changed, 125 insertions(+), 32 deletions(-) create mode 100644 ext/ldap/tests/ldap_list_read_search_parallel_references.phpt diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index cb68737d3e3d6..4d6c4bee6b9e8 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -1486,10 +1486,6 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) /* parallel search? */ if (Z_TYPE_P(link) == IS_ARRAY) { - int i, *rcs; - ldap_linkdata **lds; - zval *entry, object; - uint32_t num_links = zend_hash_num_elements(Z_ARRVAL_P(link)); if (num_links == 0) { zend_argument_must_not_be_empty_error(1); @@ -1515,7 +1511,6 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) ret = 0; goto cleanup; } - zend_hash_internal_pointer_reset(base_dn_ht); } else { if (zend_str_has_nul_byte(base_dn_str)) { zend_argument_value_error(2, "must not contain null bytes"); @@ -1538,7 +1533,6 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) ret = 0; goto cleanup; } - zend_hash_internal_pointer_reset(filter_ht); } else { if (zend_str_has_nul_byte(filter_str)) { zend_argument_value_error(3, "must not contain null bytes"); @@ -1548,73 +1542,90 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) ldap_filter = zend_string_copy(filter_str); } + int *rcs; + ldap_linkdata **lds; lds = safe_emalloc(num_links, sizeof(ldap_linkdata), 0); rcs = safe_emalloc(num_links, sizeof(*rcs), 0); - zend_hash_internal_pointer_reset(Z_ARRVAL_P(link)); - for (i=0; ilink) { + ldap_linkdata *current_ld = Z_LDAP_LINK_P(link_zv); + if (!current_ld->link) { zend_throw_error(NULL, "LDAP connection has already been closed"); ret = 0; goto cleanup_parallel; } if (num_base_dns != 0) { /* base_dn an array? */ - entry = zend_hash_get_current_data(base_dn_ht); - zend_hash_move_forward(base_dn_ht); - ldap_base_dn = zval_get_string(entry); - if (EG(exception)) { + zval *base_dn_zv = zend_hash_index_find(base_dn_ht, ldap_link_index); + ZEND_ASSERT(base_dn_zv); + ZVAL_DEREF(base_dn_zv); + if (Z_TYPE_P(base_dn_zv) != IS_STRING) { + zend_argument_type_error(2, "must be a list of strings, %s given", zend_zval_value_name(base_dn_zv)); + ret = 0; + goto cleanup_parallel; + } + ldap_base_dn = zend_string_copy(Z_STR_P(base_dn_zv)); + if (zend_str_has_nul_byte(ldap_base_dn)) { + zend_argument_value_error(2, "must not contain null bytes"); ret = 0; goto cleanup_parallel; } - // TODO check dn does not have any nul bytes } if (num_filters != 0) { /* filter an array? */ - entry = zend_hash_get_current_data(filter_ht); - zend_hash_move_forward(filter_ht); - ldap_filter = zval_get_string(entry); - if (EG(exception)) { + zval *filter_zv = zend_hash_index_find(filter_ht, ldap_link_index); + ZEND_ASSERT(filter_zv); + ZVAL_DEREF(filter_zv); + if (Z_TYPE_P(filter_zv) != IS_STRING) { + zend_argument_type_error(3, "must be a list of strings, %s given", zend_zval_value_name(filter_zv)); + ret = 0; + goto cleanup_parallel; + } + ldap_filter = zend_string_copy(Z_STR_P(filter_zv)); + if (zend_str_has_nul_byte(ldap_filter)) { + zend_argument_value_error(3, "must not contain null bytes"); ret = 0; goto cleanup_parallel; } - // TODO check filter does not have any nul bytes } if (serverctrls) { /* We have to parse controls again for each link as they use it */ _php_ldap_controls_free(&lserverctrls); - lserverctrls = _php_ldap_controls_from_array(ld->link, serverctrls, 9); + lserverctrls = _php_ldap_controls_from_array(current_ld->link, serverctrls, 9); if (lserverctrls == NULL) { - rcs[i] = -1; + rcs[ldap_link_index] = -1; + // TODO Throw an exception/cleanup? continue; } } - php_set_opts(ld->link, ldap_sizelimit, ldap_timelimit, ldap_deref, &old_ldap_sizelimit, &old_ldap_timelimit, &old_ldap_deref); + php_set_opts(current_ld->link, ldap_sizelimit, ldap_timelimit, ldap_deref, &old_ldap_sizelimit, &old_ldap_timelimit, &old_ldap_deref); /* Run the actual search */ - ldap_search_ext(ld->link, ZSTR_VAL(ldap_base_dn), scope, ZSTR_VAL(ldap_filter), ldap_attrs, ldap_attrsonly, lserverctrls, NULL, NULL, ldap_sizelimit, &rcs[i]); - lds[i] = ld; - zend_hash_move_forward(Z_ARRVAL_P(link)); - } + ldap_search_ext(current_ld->link, ZSTR_VAL(ldap_base_dn), scope, ZSTR_VAL(ldap_filter), ldap_attrs, ldap_attrsonly, lserverctrls, NULL, NULL, ldap_sizelimit, &rcs[ldap_link_index]); + lds[ldap_link_index] = current_ld; + + // TODO Reset the options of the link? + } ZEND_HASH_FOREACH_END(); array_init(return_value); /* Collect results from the searches */ - for (i=0; ilink, LDAP_RES_ANY, 1 /* LDAP_MSG_ALL */, NULL, &ldap_res); } if (rcs[i] != -1) { + zval object; object_init_ex(&object, ldap_result_ce); result = Z_LDAP_RESULT_P(&object); result->result = ldap_res; diff --git a/ext/ldap/tests/ldap_list_read_search_parallel_programming_errors.phpt b/ext/ldap/tests/ldap_list_read_search_parallel_programming_errors.phpt index 779a9fd98c59c..4f3567d9e00b7 100644 --- a/ext/ldap/tests/ldap_list_read_search_parallel_programming_errors.phpt +++ b/ext/ldap/tests/ldap_list_read_search_parallel_programming_errors.phpt @@ -13,6 +13,16 @@ $valid_filter = ""; $ldaps = [$ldap, $ldap]; +$not_list_ldaps = [ + "string1", + $ldap, +]; +try { + var_dump(ldap_list($not_list_ldaps, $valid_dn, $valid_filter)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + $dn = "dn_with\0nul_byte"; try { var_dump(ldap_list($ldaps, $dn, $valid_filter)); @@ -58,11 +68,46 @@ try { echo $e::class, ': ', $e->getMessage(), PHP_EOL; } +$list_not_all_strings = [ + 42, + "string2", +]; +try { + var_dump(ldap_list($ldaps, $list_not_all_strings, $valid_filter)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump(ldap_list($ldaps, $valid_dn, $list_not_all_strings)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$list_strings_with_nul_bytes = [ + "string\0nul_byte", + "string2", +]; +try { + var_dump(ldap_list($ldaps, $list_strings_with_nul_bytes, $valid_filter)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump(ldap_list($ldaps, $valid_dn, $list_strings_with_nul_bytes)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + ?> --EXPECT-- +ValueError: ldap_list(): Argument #1 ($ldap) must be a list of LDAP\Connection ValueError: ldap_list(): Argument #2 ($base) must not contain null bytes ValueError: ldap_list(): Argument #3 ($filter) must not contain null bytes ValueError: ldap_list(): Argument #2 ($base) must be a list ValueError: ldap_list(): Argument #3 ($filter) must be a list ValueError: ldap_list(): Argument #2 ($base) must be the same size as argument #1 ValueError: ldap_list(): Argument #3 ($filter) must be the same size as argument #1 +TypeError: ldap_list(): Argument #2 ($base) must be a list of strings, int given +TypeError: ldap_list(): Argument #3 ($filter) must be a list of strings, int given +ValueError: ldap_list(): Argument #2 ($base) must not contain null bytes +ValueError: ldap_list(): Argument #3 ($filter) must not contain null bytes \ No newline at end of file diff --git a/ext/ldap/tests/ldap_list_read_search_parallel_references.phpt b/ext/ldap/tests/ldap_list_read_search_parallel_references.phpt new file mode 100644 index 0000000000000..321cb1cff6c1c --- /dev/null +++ b/ext/ldap/tests/ldap_list_read_search_parallel_references.phpt @@ -0,0 +1,37 @@ +--TEST-- +Programming errors (Value/Type errors) for parallel usage of ldap_list(), ldap_read(), and ldap_search() with references +--EXTENSIONS-- +ldap +--FILE-- +getMessage(), PHP_EOL; +} +try { + var_dump(ldap_list($ldaps, $valid_dn, $list_with_ref_nul_byte)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +ValueError: ldap_list(): Argument #2 ($base) must not contain null bytes +ValueError: ldap_list(): Argument #3 ($filter) must not contain null bytes From 624c6ba89db828d68845b63a48601c9ff3052806 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 28 Sep 2024 17:18:21 +0100 Subject: [PATCH 5/6] ext/ldap: Remove unnecessary copy I am faily certain this was just leaking memory --- ext/ldap/ldap.c | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index 4d6c4bee6b9e8..e1eb295fca3f8 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -1403,10 +1403,11 @@ static void php_set_opts(LDAP *ldap, int sizelimit, int timelimit, int deref, in static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) { zval *link, *attrs = NULL, *serverctrls = NULL; - zend_string *base_dn_str, *filter_str; - HashTable *base_dn_ht, *filter_ht; + HashTable *base_dn_ht = NULL; + zend_string *base_dn_str = NULL; + HashTable *filter_ht = NULL; + zend_string *filter_str = NULL; zend_long attrsonly, sizelimit, timelimit, deref; - zend_string *ldap_filter = NULL, *ldap_base_dn = NULL; char **ldap_attrs = NULL; ldap_linkdata *ld = NULL; ldap_resultdata *result; @@ -1486,6 +1487,8 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) /* parallel search? */ if (Z_TYPE_P(link) == IS_ARRAY) { + const zend_string *ldap_base_dn = NULL; + const zend_string *ldap_filter = NULL; uint32_t num_links = zend_hash_num_elements(Z_ARRVAL_P(link)); if (num_links == 0) { zend_argument_must_not_be_empty_error(1); @@ -1517,7 +1520,7 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) ret = 0; goto cleanup; } - ldap_base_dn = zend_string_copy(base_dn_str); + ldap_base_dn = base_dn_str; } uint32_t num_filters = 0; /* If 0 this means we are working with a unique base dn */ @@ -1539,7 +1542,7 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) ret = 0; goto cleanup; } - ldap_filter = zend_string_copy(filter_str); + ldap_filter = filter_str; } int *rcs; @@ -1573,7 +1576,7 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) ret = 0; goto cleanup_parallel; } - ldap_base_dn = zend_string_copy(Z_STR_P(base_dn_zv)); + ldap_base_dn = Z_STR_P(base_dn_zv); if (zend_str_has_nul_byte(ldap_base_dn)) { zend_argument_value_error(2, "must not contain null bytes"); ret = 0; @@ -1589,7 +1592,7 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) ret = 0; goto cleanup_parallel; } - ldap_filter = zend_string_copy(Z_STR_P(filter_zv)); + ldap_filter = Z_STR_P(filter_zv); if (zend_str_has_nul_byte(ldap_filter)) { zend_argument_value_error(3, "must not contain null bytes"); ret = 0; @@ -1651,14 +1654,12 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) ret = 0; goto cleanup; } - ldap_base_dn = zend_string_copy(base_dn_str); if (!filter_str) { zend_argument_type_error(3, "must be of type string when argument #1 ($ldap) is an LDAP instance"); ret = 0; goto cleanup; } - ldap_filter = zend_string_copy(filter_str); if (serverctrls) { lserverctrls = _php_ldap_controls_from_array(ld->link, serverctrls, 9); @@ -1671,7 +1672,7 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) php_set_opts(ld->link, ldap_sizelimit, ldap_timelimit, ldap_deref, &old_ldap_sizelimit, &old_ldap_timelimit, &old_ldap_deref); /* Run the actual search */ - ldap_errno = ldap_search_ext_s(ld->link, ZSTR_VAL(ldap_base_dn), scope, ZSTR_VAL(ldap_filter), ldap_attrs, ldap_attrsonly, lserverctrls, NULL, NULL, ldap_sizelimit, &ldap_res); + ldap_errno = ldap_search_ext_s(ld->link, ZSTR_VAL(base_dn_str), scope, ZSTR_VAL(filter_str), ldap_attrs, ldap_attrsonly, lserverctrls, NULL, NULL, ldap_sizelimit, &ldap_res); if (ldap_errno != LDAP_SUCCESS && ldap_errno != LDAP_SIZELIMIT_EXCEEDED @@ -1711,12 +1712,6 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) /* Restoring previous options */ php_set_opts(ld->link, old_ldap_sizelimit, old_ldap_timelimit, old_ldap_deref, &ldap_sizelimit, &ldap_timelimit, &ldap_deref); } - if (ldap_filter) { - zend_string_release(ldap_filter); - } - if (ldap_base_dn) { - zend_string_release(ldap_base_dn); - } if (ldap_attrs != NULL) { efree(ldap_attrs); } From e20551434eea8439dc8ff989d540296d4e407b40 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 28 Sep 2024 17:42:22 +0100 Subject: [PATCH 6/6] ext/ldap: Print correct type --- ext/ldap/ldap.c | 6 +++--- ...p_list_read_search_programming_errors.phpt | 21 +++++++++++++++++++ ext/ldap/tests/ldap_search_error.phpt | 4 ++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index e1eb295fca3f8..90c665f38f496 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -1650,13 +1650,13 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) } if (!base_dn_str) { - zend_argument_type_error(2, "must be of type string when argument #1 ($ldap) is an LDAP instance"); + zend_argument_type_error(2, "must be of type string when argument #1 ($ldap) is an LDAP\\Connection instance"); ret = 0; goto cleanup; } if (!filter_str) { - zend_argument_type_error(3, "must be of type string when argument #1 ($ldap) is an LDAP instance"); + zend_argument_type_error(3, "must be of type string when argument #1 ($ldap) is an LDAP\\Connection instance"); ret = 0; goto cleanup; } @@ -1704,7 +1704,7 @@ static void php_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope) result->result = ldap_res; } } else { - zend_argument_type_error(1, "must be of type LDAP|array, %s given", zend_zval_value_name(link)); + zend_argument_type_error(1, "must be of type LDAP\\Connection|array, %s given", zend_zval_value_name(link)); } cleanup: diff --git a/ext/ldap/tests/ldap_list_read_search_programming_errors.phpt b/ext/ldap/tests/ldap_list_read_search_programming_errors.phpt index ee016bde99b23..59e227d818ecf 100644 --- a/ext/ldap/tests/ldap_list_read_search_programming_errors.phpt +++ b/ext/ldap/tests/ldap_list_read_search_programming_errors.phpt @@ -11,6 +11,24 @@ $ldap = ldap_connect('ldap://127.0.0.1:3333'); $valid_dn = "cn=userA,something"; $valid_filter = ""; +try { + var_dump(ldap_list(42, $valid_dn, $valid_filter)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump(ldap_list($ldap, [$valid_dn], $valid_filter)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump(ldap_list($ldap, $valid_dn, [$valid_filter])); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + $not_list = [ "attrib1", "wat" => "attrib2", @@ -59,6 +77,9 @@ try { ?> --EXPECT-- +TypeError: ldap_list(): Argument #1 ($ldap) must be of type LDAP\Connection|array, int given +TypeError: ldap_list(): Argument #2 ($base) must be of type string when argument #1 ($ldap) is an LDAP\Connection instance +TypeError: ldap_list(): Argument #3 ($filter) must be of type string when argument #1 ($ldap) is an LDAP\Connection instance ValueError: ldap_list(): Argument #4 ($attributes) must be a list TypeError: ldap_list(): Argument #4 ($attributes) must be a list of strings, int given ValueError: ldap_list(): Argument #4 ($attributes) must not contain strings with any null bytes diff --git a/ext/ldap/tests/ldap_search_error.phpt b/ext/ldap/tests/ldap_search_error.phpt index 13613ba9d5c35..04d8af46f5982 100644 --- a/ext/ldap/tests/ldap_search_error.phpt +++ b/ext/ldap/tests/ldap_search_error.phpt @@ -64,5 +64,5 @@ ldap_search(): Argument #4 ($attributes) must be a list ldap_search(): Argument #1 ($ldap) must not be empty ldap_search(): Argument #2 ($base) must be the same size as argument #1 ldap_search(): Argument #3 ($filter) must be the same size as argument #1 -ldap_search(): Argument #2 ($base) must be of type string when argument #1 ($ldap) is an LDAP instance -ldap_search(): Argument #3 ($filter) must be of type string when argument #1 ($ldap) is an LDAP instance +ldap_search(): Argument #2 ($base) must be of type string when argument #1 ($ldap) is an LDAP\Connection instance +ldap_search(): Argument #3 ($filter) must be of type string when argument #1 ($ldap) is an LDAP\Connection instance