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: [email protected]
Reporter: [email protected]
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;
}
```
--
You are receiving this mail because:
You are on the CC list for the issue.