https://bugs.openldap.org/show_bug.cgi?id=10446
Issue ID: 10446 Summary: memory leak in authzPrettyNormal 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: saslauthz.c
## Executive Summary
A memory leak of 14 bytes per search operation was identified and fixed in the `authzPrettyNormal()` function. The leak occurred during LDAP search filter parsing when processing authorization values (authzDN/authzTo/authzFrom attributes).
## Issue Description
### Symptoms - LeakSanitizer detected a 14-byte memory leak per affected operation - Leak occurred during search operations with authorization filters - Memory allocated by `ldap_url_desc2str()` was never freed
### Detection ``` ==50210==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 14 byte(s) in 1 object(s) allocated from: #0 0x55d5617e1614 in malloc /src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:67:3 #1 0x55d561dd5f04 in ber_memalloc_x /src/openldap/libraries/liblber/memory.c:228:9 #2 0x55d561d6f2ba in ldap_url_desc2str /src/openldap/libraries/libldap/url.c:803:6 #3 0x55d5619df587 in authzPrettyNormal /src/openldap/servers/slapd/saslauthz.c:843:23 #4 0x55d5619ddf5e in authzNormalize /src/openldap/servers/slapd/saslauthz.c:885:7 #5 0x55d561902b22 in asserted_value_validate_normalize /src/openldap/servers/slapd/value.c:170:8 #6 0x55d5618f8ac4 in get_ava /src/openldap/servers/slapd/ava.c:121:7 #7 0x55d5618688d0 in get_filter0 /src/openldap/servers/slapd/filter.c:151:9 #8 0x55d561872cc7 in get_filter_list /src/openldap/servers/slapd/filter.c:350:9 #9 0x55d561868595 in get_filter0 /src/openldap/servers/slapd/filter.c:231:9 #10 0x55d561872cc7 in get_filter_list /src/openldap/servers/slapd/filter.c:350:9 #11 0x55d561868595 in get_filter0 /src/openldap/servers/slapd/filter.c:231:9 #12 0x55d561872cc7 in get_filter_list /src/openldap/servers/slapd/filter.c:350:9 #13 0x55d561868595 in get_filter0 /src/openldap/servers/slapd/filter.c:231:9 #14 0x55d5618ad657 in do_search /src/openldap/servers/slapd/search.c:126:15
```
## Root Cause Analysis
### Memory Allocation Mismatch
The issue stems from a mismatch between memory allocation strategies:
1. **Function Call Chain:** ``` get_ava() → asserted_value_validate_normalize() [with ctx=op->o_tmpmemctx] → authzNormalize() → authzPrettyNormal() → ldap_url_desc2str() ```
2. **The Problem:** - `ldap_url_desc2str()` allocates memory using `LDAP_MALLOC()` (global heap) - The calling code expects memory allocated via `slap_sl_malloc(ctx)` (context-aware) - Context-aware memory is automatically freed when the operation completes - Global heap memory requires explicit `ber_memfree()` call - No explicit free was performed, causing the leak
### Code Location
**File:** `servers/slapd/saslauthz.c` **Function:** `authzPrettyNormal()` **Lines:** 842-849 (original code)
```c // BEFORE (leaked memory): ludp->lud_port = 0; normalized->bv_val = ldap_url_desc2str( ludp ); // ← Allocates with LDAP_MALLOC if ( normalized->bv_val ) { normalized->bv_len = strlen( normalized->bv_val ); } else { rc = LDAP_INVALID_SYNTAX; } ```
## Fix Implementation
### Solution Strategy
Copy the result from `ldap_url_desc2str()` into context-allocated memory, then free the original allocation.
### Modified Code
```diff diff --git a/servers/slapd/saslauthz.c b/servers/slapd/saslauthz.c index 86ef944..1833921 100644 --- a/servers/slapd/saslauthz.c +++ b/servers/slapd/saslauthz.c @@ -840,12 +840,16 @@ is_dn: bv.bv_len = val->bv_len - ( bv.bv_val - val->bv_val ); }
ludp->lud_port = 0; - normalized->bv_val = ldap_url_desc2str( ludp ); - if ( normalized->bv_val ) { - normalized->bv_len = strlen( normalized->bv_val ); - - } else { - rc = LDAP_INVALID_SYNTAX; + { + char *tmpstr = ldap_url_desc2str( ludp ); + if ( tmpstr ) { + normalized->bv_len = strlen( tmpstr ); + normalized->bv_val = slap_sl_malloc( normalized->bv_len + 1, ctx ); + strcpy( normalized->bv_val, tmpstr ); + ber_memfree( tmpstr ); + } else { + rc = LDAP_INVALID_SYNTAX; + } }
done: ```
### Fix Details
1. Store `ldap_url_desc2str()` result in temporary variable `tmpstr` 2. Allocate new memory using `slap_sl_malloc(ctx)` (context-aware allocator) 3. Copy the string content to the new memory 4. Free the original allocation using `ber_memfree()` 5. Result: `normalized->bv_val` now contains context-managed memory
## Impact Assessment
### Affected Operations - LDAP search operations with authorization filters - Filters containing authzDN, authzTo, or authzFrom attributes - Any operation using authz URL normalization
## 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; } ```