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: [email protected]
          Reporter: [email protected]
  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;
}
```

-- 
You are receiving this mail because:
You are on the CC list for the issue.

Reply via email to