Merge 'ldap: fix double-free of LDAPMessage in poll_results()' from Andrzej Jackowski

In the unregistered-ID branch, ldap_msgfree() was called on a result
already owned by an RAII ldap_msg_ptr, causing a double-free on scope
exit. Remove the redundant manual free.

Fixes: SCYLLADB-1344

Backport: 2026.1, 2025.4, 2025.1 - it's a memory corruption, with a one-line fix, so better backport it everywhere.

Closes scylladb/scylladb#29302

* github.com:scylladb/scylladb:
  test: ldap: add regression test for double-free on unregistered message ID
  ldap: fix double-free of LDAPMessage in poll_results()

(cherry picked from commit 895fdb6d29)

Closes scylladb/scylladb#29393

Closes scylladb/scylladb#29454
This commit is contained in:
Marcin Maliszkiewicz
2026-04-07 17:27:43 +02:00
committed by Botond Dénes
parent 52a3ed4312
commit 5468cd49da
2 changed files with 29 additions and 2 deletions

View File

@@ -437,7 +437,6 @@ void ldap_connection::poll_results() {
const auto found = _msgid_to_promise.find(id);
if (found == _msgid_to_promise.end()) {
mylog.error("poll_results: got valid result for unregistered id {}, dropping it", id);
ldap_msgfree(result);
} else {
found->second.set_value(std::move(result_ptr));
_msgid_to_promise.erase(found);

View File

@@ -235,7 +235,7 @@ SEASTAR_THREAD_TEST_CASE(multiple_outstanding_operations_on_failing_connection)
mylog.trace("multiple_outstanding_operations_on_failing_connection");
with_ldap_connection(local_fail_inject_address, [] (ldap_connection& c) {
mylog.trace("multiple_outstanding_operations_on_failing_connection: invoking bind");
bind(c).handle_exception(&ignore).get();;
bind(c).handle_exception(&ignore).get();
std::vector<future<ldap_msg_ptr>> results_base;
for (size_t i = 0; i < 10; ++i) {
@@ -291,3 +291,31 @@ SEASTAR_THREAD_TEST_CASE(severed_connection_yields_exceptional_future) {
}
});
}
// Requires ASAN or valgrind to reliably detect the double-free.
SEASTAR_THREAD_TEST_CASE(unregistered_msgid_double_free) {
set_defbase();
with_ldap_connection(local_ldap_address, [] (ldap_connection& c) {
const auto bind_res = bind(c).get();
BOOST_REQUIRE_EQUAL(LDAP_RES_BIND, ldap_msgtype(bind_res.get()));
// Bypass the public API to send a search without registering its
// message ID, so poll_results() hits the unregistered-ID branch.
int msgid = -1;
const int rc = ldap_search_ext(c.get_ldap(), const_cast<char*>(base_dn), LDAP_SCOPE_SUBTREE,
/*filter=*/nullptr,
/*attrs=*/nullptr,
/*attrsonly=*/0,
/*serverctrls=*/nullptr,
/*clientctrls=*/nullptr,
/*timeout=*/nullptr,
/*sizelimit=*/0, &msgid);
BOOST_REQUIRE_EQUAL(LDAP_SUCCESS, rc);
BOOST_REQUIRE_NE(-1, msgid);
// A public-API search forces poll_results() to process the
// unregistered response before returning.
const auto dummy = search(c, base_dn).get();
BOOST_REQUIRE(dummy.get());
});
}