https://bugs.openldap.org/show_bug.cgi?id=10447
Issue ID: 10447 Summary: memory leak in the LDAP chaining behavior control parser (`ldap_chain_parse_ctrl`) Product: OpenLDAP Version: 2.6.12 Hardware: x86_64 OS: Linux Status: UNCONFIRMED Keywords: needs_review Severity: normal Priority: --- Component: slapd Assignee: bugs@openldap.org Reporter: kangyang126@gmail.com Target Milestone: ---
# Memory Leak Fix Report: LDAP Chain Control Parser
## Executive Summary
Fixed a memory leak in the LDAP chaining behavior control parser (`ldap_chain_parse_ctrl`) that was leaking 80 bytes per failed control parsing operation. The leak was detected by LeakSanitizer during fuzzing operations.
## Issue Details
**File**: `servers/slapd/back-ldap/chain.c` **Function**: `ldap_chain_parse_ctrl`
## Root Cause Analysis
The `ldap_chain_parse_ctrl` function allocates a BER (Basic Encoding Rules) structure at line 2222:
```c ber = ber_init( &ctrl->ldctl_value ); ```
This allocation is properly freed at the end of the function (line 2303) in the success path:
```c (void) ber_free( ber, 1 ); ```
However, the function contains **five error handling paths** that return `LDAP_PROTOCOL_ERROR` before reaching the cleanup code, causing the BER structure to leak in these failure scenarios:
1. **Invalid resolveBehavior tag** (line 2231-2235) 2. **Unknown resolveBehavior value** (line 2254-2258) 3. **continuationBehavior decoding error** (line 2263-2267) 4. **Unknown continuationBehavior value** (line 2291-2295) 5. **Final sequence parsing error** (line 2298-2302)
## LeakSanitizer Stack Trace
``` ==76274==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 80 byte(s) in 1 object(s) allocated from: #0 0x55dd3756d7d9 in calloc #1 0x55dd37b62198 in ber_memcalloc_x #2 0x55dd37b62198 in ber_memcalloc #3 0x55dd37b5def2 in ber_alloc_t #4 0x55dd37b5def2 in ber_init #5 0x55dd37a14e97 in ldap_chain_parse_ctrl (chain.c:2222) #6 0x55dd3771714c in slap_parse_ctrl #7 0x55dd37718111 in get_ctrls2 #8 0x55dd37752c26 in do_add ```
## Solution Implemented
Added `ber_free( ber, 1 );` before each early return statement to ensure proper cleanup in all code paths:
### Fix 1: resolveBehavior decoding error (line 2232) ```diff diff --git a/servers/slapd/back-ldap/chain.c b/servers/slapd/back-ldap/chain.c index a789e7a..8fdd7a5 100644 --- a/servers/slapd/back-ldap/chain.c +++ b/servers/slapd/back-ldap/chain.c @@ -2229,6 +2229,7 @@ ldap_chain_parse_ctrl( /* FIXME: since the whole SEQUENCE is optional, * should we accept no enumerations at all? */ if ( tag != LBER_ENUMERATED ) { + ber_free( ber, 1 ); rs->sr_text = "Chaining behavior control: resolveBehavior decoding error"; return LDAP_PROTOCOL_ERROR; } @@ -2251,6 +2252,7 @@ ldap_chain_parse_ctrl( break;
default: + ber_free( ber, 1 ); rs->sr_text = "Chaining behavior control: unknown resolveBehavior"; return LDAP_PROTOCOL_ERROR; } @@ -2259,6 +2261,7 @@ ldap_chain_parse_ctrl( if ( tag == LBER_ENUMERATED ) { tag = ber_scanf( ber, "e", &behavior ); if ( tag == LBER_ERROR ) { + ber_free( ber, 1 ); rs->sr_text = "Chaining behavior control: continuationBehavior decoding error"; return LDAP_PROTOCOL_ERROR; } @@ -2286,12 +2289,14 @@ ldap_chain_parse_ctrl( break;
default: + ber_free( ber, 1 ); rs->sr_text = "Chaining behavior control: unknown continuationBehavior"; return LDAP_PROTOCOL_ERROR; } }
if ( ( ber_scanf( ber, /* { */ "}") ) == LBER_ERROR ) { + ber_free( ber, 1 ); rs->sr_text = "Chaining behavior control: decoding error"; return LDAP_PROTOCOL_ERROR; } ```
## 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; } ```