https://bugs.openldap.org/show_bug.cgi?id=10448
Issue ID: 10448 Summary: Memory leak in syncprov_parseCtrl Product: OpenLDAP Version: 2.6.12 Hardware: x86_64 OS: Linux Status: UNCONFIRMED Keywords: needs_review Severity: normal Priority: --- Component: overlays Assignee: bugs@openldap.org Reporter: kangyang126@gmail.com Target Milestone: ---
# ASAN Leak Report: Sync Provider Control
## Component * **File:** `servers/slapd/overlays/syncprov.c` * **Function:** `syncprov_parseCtrl`
## Issue Description **LeakSanitizer Error:** Memory leak of `sync_control` structure.
```plaintext ==266290==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 80 byte(s) in 1 object(s) allocated from: #0 0x555555964614 in malloc /src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:67:3 #1 0x555555f58a04 in ber_memalloc_x /src/openldap/libraries/liblber/memory.c:228:9 #2 0x555555a7cb06 in ch_malloc /src/openldap/servers/slapd/ch_malloc.c:54:23 #3 0x555555b3ba6c in slap_sl_calloc /src/openldap/servers/slapd/sl_malloc.c:401:12 #4 0x555555ca1734 in syncprov_parseCtrl /src/openldap/servers/slapd/overlays/syncprov.c:4333:7 #5 0x555555b0e13c in slap_parse_ctrl /src/openldap/servers/slapd/controls.c:738:10 #6 0x555555b0f101 in get_ctrls2 /src/openldap/servers/slapd/controls.c:929:16 #7 0x555555a30eae in do_search /src/openldap/servers/slapd/search.c:194:6 #8 0x5555559e155d in connection_operation /src/openldap/servers/slapd/connection.c:1137:7 #9 0x5555559e0327 in connection_read_thread /src/openldap/servers/slapd/connection.c:1289:14 #10 0x5555559a869a in LLVMFuzzerTestOneInput /src/fuzz_slapd.c:88:5 ```
The `syncprov` overlay allocates a specific `sync_control` structure and attaches it to the operation's control list (`op->o_controls`) when parsing the `LDAP_CONTROL_SYNC`. * **Leak:** The core server's cleanup routine (`slap_free_ctrls`) is unaware of this overlay-specific structure. If the operation is abandoned or fails (e.g., during parsing of subsequent controls), the `sync_control` and its internal allocations (context CSNs, session IDs) are never freed.
## Fix * **Cleanup Callback:** Defined a new callback function `syncprov_ctrl_cleanup` that properly frees the `sync_control` structure. * **Registration:** Updated `syncprov_parseCtrl` to register this callback (`op->o_callback`) immediately after allocating the control. This ensures `slap_cleanup_play` invokes the cleanup routine during operation teardown, preventing the leak.
## Diff ```diff diff --git a/servers/slapd/overlays/syncprov.c b/servers/slapd/overlays/syncprov.c index 042d742..7257f79 100644 --- a/servers/slapd/overlays/syncprov.c +++ b/servers/slapd/overlays/syncprov.c @@ -4246,6 +4246,26 @@ syncprov_db_destroy( return 0; }
+static int +syncprov_ctrl_cleanup( Operation *op, SlapReply *rs ) +{ + if ( op->o_controls[slap_cids.sc_LDAPsync] ) { + sync_control *sr = op->o_controls[slap_cids.sc_LDAPsync]; + op->o_controls[slap_cids.sc_LDAPsync] = NULL; + if ( sr->sr_state.ctxcsn ) { + ber_bvarray_free_x( sr->sr_state.ctxcsn, op->o_tmpmemctx ); + } + if ( sr->sr_state.sids ) { + op->o_tmpfree( sr->sr_state.sids, op->o_tmpmemctx ); + } + if ( sr->sr_state.octet_str.bv_val ) { + op->o_tmpfree( sr->sr_state.octet_str.bv_val, op->o_tmpmemctx ); + } + op->o_tmpfree( sr, op->o_tmpmemctx ); + } + return slap_freeself_cb( op, rs ); +} + static int syncprov_parseCtrl ( Operation *op, SlapReply *rs, @@ -4353,6 +4373,13 @@ static int syncprov_parseCtrl (
op->o_sync_mode |= mode; /* o_sync_mode shares o_sync */
+ { + slap_callback *cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx ); + cb->sc_cleanup = syncprov_ctrl_cleanup; + cb->sc_next = op->o_callback; + op->o_callback = cb; + } + return LDAP_SUCCESS; } ```
## Harness ``` #define _GNU_SOURCE /* For memfd_create */ #include "portable.h" #include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/types.h> #include <signal.h>
#include "slap.h"
/* 1. Fix function declarations to match return types in OpenLDAP source code */ extern void slap_sl_mem_init( void ); extern int slapd_daemon_init( const char *urls ); extern int extops_init( void ); /* Returns int */ extern void lutil_passwd_init( void ); extern int slap_init( int mode, const char *name ); extern int read_config( const char *fname, const char *dname ); extern int connections_init( void ); /* Returns int */ extern int slap_startup( Backend *be ); void slapd_daemon_nothread( void ) {} extern int ldap_pvt_thread_initialize( void ); /* Returns int */ extern void* ldap_pvt_thread_pool_context( void ); extern void* connection_read_thread( void *ctx, void *arg ); extern void connection_closing( Connection *c, const char *why ); extern int connection_resched( Connection *c ); extern Connection* connection_init( int sfd, Listener *l, const char *authid, const char *dns, int flags, slap_ssf_t ssf, struct berval *authid_out ); extern ldap_pvt_thread_pool_t connection_pool;
static Listener *global_listener = NULL; static struct berval authid_bv = {0, ""};
int LLVMFuzzerInitialize(int *argc, char ***argv) { signal(SIGPIPE, SIG_IGN); slap_debug = 0; slap_sl_mem_init();
if ( 0 != slapd_daemon_init("ldapi://%2Ftmp%2Ffuzz.sock") ) return 1;
extops_init(); lutil_passwd_init();
if ( slap_init(SLAP_SERVER_MODE, "slapd-fuzz") ) return 2;
read_config( NULL, NULL );
connections_init(); slap_startup(NULL);
slapd_daemon_nothread(); ldap_pvt_thread_initialize();
global_listener = slapd_get_listeners()[0]; if (!global_listener) return 3;
return 0; }
#include <sys/socket.h>
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { if (Size < 2 || Size > 65536) return 0;
/* 2. Use socketpair to simulate socket read/write */ int sv[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) return 0;
if (write(sv[0], Data, Size) != (ssize_t)Size) { close(sv[0]); close(sv[1]); return 0; } /* Close write end so server receives EOF after reading data */ close(sv[0]);
/* 3. Create connection (server uses sv[1]) */ Connection *c = connection_init(sv[1], global_listener, "fuzz", "IP=127.0.0.1", 0, 0, &authid_bv); if (!c) { close(sv[1]); return 0; }
void* ctx = ldap_pvt_thread_pool_context();
/* Execute parsing logic */ connection_read_thread(ctx, (void*)(long)sv[1]);
/* Wait for thread pool to complete all tasks to prevent background threads from accessing freed connection */ int active = 0, pending = 0; do { ldap_pvt_thread_pool_query(&connection_pool, LDAP_PVT_THREAD_POOL_PARAM_PENDING, &pending); ldap_pvt_thread_pool_query(&connection_pool, LDAP_PVT_THREAD_POOL_PARAM_ACTIVE, &active); if (active == 0 && pending == 0) break; ldap_pvt_thread_yield(); } while (1);
/* 4. Thoroughly clean up manually, avoiding unstable macros * Directly manipulate queue pointers to ensure no 'error: use of undeclared identifier o_next' */ Operation *op; while ((op = LDAP_STAILQ_FIRST(&c->c_ops)) != NULL) { LDAP_STAILQ_REMOVE_HEAD(&c->c_ops, o_next); LDAP_STAILQ_NEXT(op, o_next) = NULL; slap_op_free(op, ctx); } LDAP_STAILQ_INIT(&c->c_ops);
/* Close and reclaim Connection memory */ connection_closing(c, "fuzz complete"); connection_resched(c);
/* ldap_pvt_thread_pool_resume(&connection_pool); */
return 0; } ```
https://bugs.openldap.org/show_bug.cgi?id=10448
Howard Chu hyc@openldap.org changed:
What |Removed |Added ---------------------------------------------------------------------------- Assignee|bugs@openldap.org |hyc@openldap.org
https://bugs.openldap.org/show_bug.cgi?id=10448
--- Comment #1 from Howard Chu hyc@openldap.org --- This does not appear to be a valid report. The sync_control structure is allocated in the operation's tmpmemctx, therefore it is implicitly disposed of when the operation completes.
The stack trace you provide would only have occurred if the operation was invoked without any tmpmemctx provided, which slapd doesn't do.
https://bugs.openldap.org/show_bug.cgi?id=10448
--- Comment #2 from KY kangyang126@gmail.com --- Thank you for taking the time to review this report. I appreciate your insight about tmpmemctx allocations—it helped me dig deeper into the slab allocator behavior.
You're absolutely right that sync_control is allocated via o_tmpcalloc with a valid tmpmemctx. After further investigation, I believe this is still a valid issue, though the root cause is more nuanced than my initial report suggested.
The core issue: op->o_controls[] entries are never explicitly freed.
While slap_free_ctrls() properly frees op->o_ctrls[] (the LDAPControl array), the overlay-specific data stored in op->o_controls[] has no corresponding cleanup path. The code relies on implicit slab reclamation.
Why does this become a leak?
This works under normal conditions. However, when the slab exhausts (complex requests with large filters, many attributes, or multiple controls), sl_malloc.c:385-390 falls back to ch_malloc(). Slab reset doesn't reclaim these overflow allocations—only explicit slap_sl_free() does.
Debug trace confirming the scenario:
DEBUG syncprov_parseCtrl: o_tmpmemctx=0x7badaf3f84a0 (valid, non-NULL) DEBUG sl_malloc: SLAB EXHAUSTED! size=80 ... avail=8 The proposed fix (cleanup callback) ensures explicit cleanup via op->o_tmpfree(), which handles both slab and overflow allocations correctly.
I hope this clarifies the issue. Please let me know if you have further questions or would like additional details.
https://bugs.openldap.org/show_bug.cgi?id=10448
Howard Chu hyc@openldap.org changed:
What |Removed |Added ---------------------------------------------------------------------------- Status|UNCONFIRMED |RESOLVED Resolution|--- |TEST
--- Comment #3 from Howard Chu hyc@openldap.org --- OK, I see. Fixed in git e9451873ae88fc97d001bc800d098e99eba49a65 Thanks.
https://bugs.openldap.org/show_bug.cgi?id=10448
Quanah Gibson-Mount quanah@openldap.org changed:
What |Removed |Added ---------------------------------------------------------------------------- Keywords|needs_review | Target Milestone|--- |2.6.13
https://bugs.openldap.org/show_bug.cgi?id=10448
--- Comment #4 from Quanah Gibson-Mount quanah@openldap.org --- head:
• fa3b4ba8 by Howard Chu at 2026-02-06T20:00:57+00:00 ITS#10448 slapo-syncprov: free parsed control when operation completes
RE26:
• b8e63221 by Howard Chu at 2026-02-13T02:08:54+00:00 ITS#10448 slapo-syncprov: free parsed control when operation completes
https://bugs.openldap.org/show_bug.cgi?id=10448
Quanah Gibson-Mount quanah@openldap.org changed:
What |Removed |Added ---------------------------------------------------------------------------- Status|RESOLVED |VERIFIED Resolution|TEST |FIXED