// Low (1.9)
CVE-2026-11786

Heap out-of-bounds read in LDIF parser str2entry_state_information_from_type

CVE ID
CVE-2026-11786
Product
389-ds-base
Severity
Low (1.9)
CVSS Vector
CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:N/A:N
CWE
CWE-125
Red Hat CVE
Red Hat CVE page

Summary

A heap out-of-bounds read vulnerability exists in the 389 Directory Server’s LDIF parser. When an administrator imports an LDIF file containing attribute types with trailing semicolons, the server reads 1 to 16 bytes past the end of a heap allocation. The bug requires local administrator access and does not crash production binaries, limiting the impact to a low-severity memory safety defect detectable only under instrumentation.

Affected Versions

PackageVersionNVRStatus
389-ds-base2.6.1389-ds-base-2.6.1-6.el9Vulnerable (confirmed — ASan-proven, aarch64)
389-ds-base1.3.11.1389-ds-base-1.3.11.1-5.el7_9Vulnerable (confirmed — code path reached via ldif2db)
389-ds-base2.7.0389-ds-base-2.7.0-10.el9_7Vulnerable (confirmed — source unchanged per git history)
389-ds-base3.1.3389-ds-base-3.1.3-7.el10_1Vulnerable (confirmed — source unchanged per git history)
389-ds-base3.0.6389-ds-base-3.0.6-17.el10_0Vulnerable (confirmed — source unchanged per git history)
389-ds-base3.1.4389-ds-base-3.1.4-6.fc42Vulnerable (confirmed — debug container on test system)
389-ds-baseHEADupstream currentVulnerable (confirmed — source verified on test system)

Affected Products

ProductVersionStatusVerification
Red Hat Enterprise Linux 7389-ds-base-1.3.11.1-5.el7_9AffectedCode path confirmed via ldif2db import on EL7 container; deletedattribute string present in libslapd.so
Red Hat Enterprise Linux 9 (CentOS Stream 9)389-ds-base-2.6.1-6.el9AffectedASan-proven heap-buffer-overflow on aarch64 instrumented build
Red Hat Enterprise Linux 9.7389-ds-base-2.7.0-10.el9_7AffectedSource unchanged per upstream git history; str2entry_state_information_from_type() identical
Red Hat Enterprise Linux 10.0 EUS389-ds-base-3.0.6-17.el10_0AffectedSource unchanged per upstream git history
Red Hat Enterprise Linux 10.1389-ds-base-3.1.3-7.el10_1AffectedSource unchanged per upstream git history
Fedora 42389-ds-base-3.1.4-6.fc42AffectedVerified via debug container on test system
Red Hat Directory Server (RHDS)All versions shipping 389-ds-baseAffectedRHDS is 389-ds-base with management tooling; same vulnerable entry.c code
Red Hat Identity Management (IdM) / FreeIPAAll versions shipping 389-ds-baseAffectedIdM uses 389-ds-base as its LDAP backend; same vulnerable entry.c code. The ldif2db import path is available to IdM administrators.
UBI 7 / UBI 9Matches corresponding RHELAffectedUBI ships the same 389-ds-base packages as the corresponding RHEL version

Architecture Validation

ArchitectureTestedResult
aarch64YESASan heap-buffer-overflow confirmed (389-ds-base-2.6.1-6.el9 instrumented build)
x86_64YESStandalone PoC triggers ASan heap-buffer-overflow on test system; production binary processes entry without crash

Root Cause

File: ldap/servers/slapd/entry.c:119-170 Function: str2entry_state_information_from_type()

Three distinct OOB read sites share the same root cause: PL_strchr()/strchr() locates a semicolon in the attribute type string, but no bounds check verifies that sufficient bytes remain before subsequent memory accesses.

Site 1: entry.c:122-123 — 4-byte indexed access (ASan-proven)

p = PL_strchr(atype->bv_val, ';');
// ...
while (p != NULL) {
    if (p[3] == 'c' && p[4] == 's' && p[5] == 'n' && p[6] == '-') {
        // CSN parsing -- reads p[3] through p[6] without bounds check

When the semicolon is within the last 6 bytes of the heap allocation, p[3] through p[6] read past the boundary.

Site 2: entry.c:164 — 16-byte strncmp read

    } else if (strncmp(p + 1, "deletedattribute", 16) == 0) {

When fewer than 17 bytes remain after the semicolon, strncmp(p+1, ...) reads up to 16 bytes past the valid allocation. This is the largest OOB read window in the function.

Site 3: entry.c:170 — 7-byte strncmp read

    } else if (strncmp(p + 1, "deleted", 7) == 0) {

When fewer than 8 bytes remain after the semicolon, strncmp(p+1, ...) reads up to 7 bytes past the valid allocation.

Allocation context

The buffer is allocated by dup_ldif_line() via slapi_ch_realloc() at util.c:1785, sized exactly to the attribute type length with no padding beyond the NUL terminator.

Proof of Concept

PoC source code: CVE-2026-11786 on GitHub

Two PoC programs and an LDIF trigger file are provided in the poc/ directory.

PoC 1: OOB Read Trigger (poc/poc-str2entry-oob.c)

Replicates the vulnerable logic from entry.c:119-124 where p[3]..p[6] are accessed after finding a semicolon via PL_strchr without bounds checking.

Build and run:

gcc -fsanitize=address -g -o poc-017 poc/poc-str2entry-oob.c
./poc-017

Expected result: AddressSanitizer reports heap-buffer-overflow READ of size 1 in check_state_info at the p[3] access. Exit code is non-zero.

PoC 2: Behavioral Oracle (poc/poc-str2entry-heap-oracle.c)

The p[3..6] comparison against 'c','s','n','-' in str2entry_state_information_from_type() creates a behavioral oracle over adjacent heap content. By varying attribute type length (shifting the semicolon position), an attacker slides the 4-byte probe window across adjacent heap objects.

Oracle outputs:

  • 0 = p[3] != 'c' (1 byte probed)
  • 1 = p[3]='c', p[4]!='s' (2 bytes probed)
  • 2 = p[3..4]='cs', p[5]!='n' (3 bytes probed)
  • 3 = p[3..5]='csn', p[6]!='-' (4 bytes probed)
  • 4 = p[3..6]='csn-' — CSN parsing path taken (different behavior)

Build and run:

# Without ASan (to see oracle behavior without crash):
gcc -g -o poc-017-oracle poc/poc-str2entry-heap-oracle.c
./poc-017-oracle

# With ASan (to prove OOB):
gcc -fsanitize=address -g -o poc-017-oracle poc/poc-str2entry-heap-oracle.c
ASAN_OPTIONS=halt_on_error=0 ./poc-017-oracle

LDIF Trigger File (poc/trigger.ldif)

dn: uid=oobtest017,dc=example,dc=com
objectClass: person
cn: x
sn: x
userCertificate;binary;: dGVzdA==

The trailing semicolon in userCertificate;binary; causes PL_strchr to find the second ; near the allocation boundary, making p[3] through p[6] read past the buffer.

Production Reproduction via ldif2db

Import the LDIF trigger file via ldif2db (requires root or Directory Manager):

ns-slapd ldif2db -D /etc/dirsrv/slapd-<instance> -i /tmp/017-oob.ldif -n userRoot

Import completes successfully (exit code 0). No crash on production (non-ASan) binary — the system allocator provides padding that absorbs the small overread. The code path through slapi_str2entry() -> str2entry_fast() -> str2entry_state_information_from_type() is confirmed reached by successful entry processing.

Impact

CVSS 3.1: 1.9 (CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:N/A:N)

ComponentValueJustification
AVL (Local)The only confirmed external input path is ldif2db, a local admin tool. The LDAP wire protocol (do_add, do_modify, do_modrdn) constructs entries via BER decoding, not slapi_str2entry().
ACH (High)Requires crafting a specific LDIF with a trailing semicolon positioned within the last bytes of the attribute type allocation.
PRH (High)ldif2db requires root or Directory Manager privileges on the server host.
UIN (None)No user interaction required beyond the admin running the import.
SU (Unchanged)The vulnerability stays within the ns-slapd process.
CL (Low)Reads 1-16 bytes of adjacent heap data. Values are compared against fixed constants ('c', 's', 'n', '-', "deletedattribute", "deleted") and are not returned to the caller, limiting information disclosure.
IN (None)Read-only access — no data modification.
AN (None)Production (non-ASan) binaries do not crash. The system allocator provides padding that absorbs the small overread. ASan-instrumented builds abort, but this is not a production scenario.

Comparable CVEs

CVEProductTypeCVSSNotes
CVE-2024-1062389-ds-baseHeap overflow in log_entry_attr (CWE-122)~5.5 (Moderate)Local, low complexity, DoS via crash. Higher than this finding because it causes an actual crash on production binaries.
CVE-2018-1054389-ds-baseOOB read in SetUnicodeStringFromUTF_8 (CWE-125)7.5 (High)Network-reachable (AV:N), unauthenticated (PR:N), DoS via crash. Much higher because it is remotely exploitable and causes a crash.
CVE-2025-14905389-ds-baseHeap overflow in schema.c (CWE-122)ModerateDifferent function (schema_attr_enum_callback), different vulnerability class.

Blast Radius

389-ds-base is a foundational component serving as the LDAP backend for both Red Hat Directory Server (RHDS) and Red Hat Identity Management (IdM/FreeIPA). It is not supported as a standalone LDAP server on RHEL.

Downstream consumers:

  • Red Hat Directory Server (RHDS): Ships 389-ds-base with management tooling. Any RHDS deployment where an administrator imports LDIF files is potentially affected.
  • Red Hat Identity Management (IdM) / FreeIPA: Uses 389-ds-base as its LDAP backend. IdM administrators who use ldif2db for data recovery or migration are affected.
  • Dogtag Certificate System: Uses 389-ds-base for its internal LDAP store. Affected if LDIF import is used for certificate database management.

Blast radius assessment: Limited. The vulnerability requires local administrator access, deliberate import of a malformed LDIF file via ldif2db, and the overread is silent on production binaries (no crash, no observable effect). An administrator who can run ldif2db already has full control over the directory data. The vulnerability does not enable privilege escalation or lateral movement.

Exploitation in the Wild

No evidence found. Searches performed across NVD, Red Hat Bugzilla, GitHub 389ds/389-ds-base issues, Debian security tracker, and CISA KEV catalog returned no matching entries. The OOB read is small (1-16 bytes), compared against fixed constants, and not returned to the caller, making information disclosure impractical.

Workaround

Validate LDIF files for malformed attribute types before importing. Reject any LDIF entry containing attribute types with trailing semicolons (e.g., userCertificate;binary;).

Pre-import validation check:

grep -nE '^[a-zA-Z][a-zA-Z0-9-]*;[^:]*;:' input.ldif && echo "REJECT: trailing semicolon in attribute type"

This does not protect against the replication changelog path (corrupted stored data), but that path requires a pre-existing database corruption condition, not external input.

Proposed Fix

Add bounds checking before all three access sites. Compute remaining length once after strchr returns and gate each branch on the required minimum length.

while (p != NULL) {
    size_t remaining = strlen(p);  /* includes the ';' at p[0] */
    if (remaining >= 7 && p[3] == 'c' && p[4] == 's' && p[5] == 'n' && p[6] == '-') {
        /* ... existing CSN parsing (line 123) ... */
    } else if (remaining >= 17 && strncmp(p + 1, "deletedattribute", 16) == 0) {
        /* ... existing deleted-attribute handling (line 164) ... */
    } else if (remaining >= 8 && strncmp(p + 1, "deleted", 7) == 0) {
        /* ... existing deleted-value handling (line 170) ... */
    }
    p = strchr(p + 1, ';');
}

The remaining >= N checks ensure that all indexed accesses and strncmp reads stay within the allocation. The minimum lengths are: 7 for p[3..6] (CSN), 17 for p+1 + 16-byte strncmp (deletedattribute), and 8 for p+1 + 7-byte strncmp (deleted).

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