// Moderate (5.9)
CVE-2026-11788

NULL pointer dereference in deref control plugin BER parser

CVE ID
CVE-2026-11788
Product
389-ds-base
Severity
Moderate (5.9)
CVSS Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE
CWE-476
Red Hat CVE
Red Hat CVE page

Summary

A flaw in the 389 Directory Server’s dereference control plugin allows an unauthenticated attacker to crash the LDAP server when the system is under memory pressure. The deref plugin, enabled by default, fails to check for a memory allocation failure before using the result, causing the server process to terminate. This results in a complete LDAP service outage requiring administrator intervention to restart.

Affected Versions

PackageVersionNVRStatus
389-ds-base (upstream)1.2.6 through 3.2.0-devN/A (upstream source)Vulnerable (confirmed — source review)
389-ds-base (RHEL 8)1.4.x389-ds-base-1.4.3.39-8.module+el8.10 (latest known pre-2026 NVR)Vulnerable (confirmed — code present in all 1.4.x)
389-ds-base (RHEL 9)2.0.x-2.8.x389-ds-base-2.7.0-10.el9_7 (RHSA-2026:3189)Vulnerable (confirmed — code present in all 2.x)
389-ds-base (RHEL 10)3.0.x-3.1.x389-ds-base-3.0.6-17.el10_0 (RHSA-2026:3504)Vulnerable (confirmed — code present in all 3.x)
389-ds-base (Fedora 42)3.1.4389-ds-base-3.1.4-6.fc42.x86_64Vulnerable (confirmed — crash reproduced via GDB)

Note: The vulnerable deref_parse_ctrl_value() function has never contained a NULL check for the ber_init() return value since the deref plugin was introduced in 389-ds-base 1.2.6 (~2010). All versions since then are affected.

Affected Products

ProductVersion389-ds-base versionStatusVerification
389 Directory Server (upstream)all1.2.6 through 3.2.0 (HEAD)AffectedSource review: deref_parse_ctrl_value() has no NULL check for ber_init() in any version since plugin introduction (2010)
RHEL 8 (IdM / FreeIPA)8.0-8.101.4.x (389-ds:1.4 module)AffectedShips OpenLDAP 2.4.46 — crash is SIGSEGV (write to *(NULL+0x28) in ber_skip_tag). RHEL 8 is in Maintenance Support 2 (full support ended May 2024).
RHEL 9 (IdM / FreeIPA)9.0-9.62.0.x-2.8.xAffectedShips OpenLDAP 2.6.x — crash is SIGABRT (assert(ber != NULL) in ber_tag_and_rest). NVR 389-ds-base-2.7.0-10.el9_7 confirmed via RHSA-2026:3189.
RHEL 10 (IdM / FreeIPA)10.0-10.13.0.x-3.1.3AffectedAll RHEL 10 builds ship 389-ds-base 3.x with vulnerable deref plugin. NVR 389-ds-base-3.0.6-17.el10_0 confirmed via RHSA-2026:3504.
Red Hat Directory Server 1111.x1.4.xAffectedRHDS 11 ships same 389-ds-base as RHEL 8 (1.4.x). Same SIGSEGV crash path as RHEL 8.
Red Hat Directory Server 1212.0-12.72.xAffectedRHDS 12 ships same 389-ds-base as RHEL 9 AppStream. Same SIGABRT crash path as RHEL 9.
Fedora39-423.0.x-3.2.0AffectedTracks upstream; deref plugin enabled by default. Crash confirmed on Fedora 42 (389-ds-base-3.1.4-6.fc42.x86_64) via GDB-controlled reproduction.
CentOS Stream 992.8.0AffectedConfirmed via dist-git; same code as upstream.
CentOS Stream 10103.2.0AffectedConfirmed via dist-git; same code as upstream.

Architecture Validation

ArchitectureTestedResult
x86_64YESCrash confirmed via GDB-controlled reproduction on Fedora 42 (389-ds-base-3.1.4-6.fc42.x86_64) — SIGABRT
aarch64NONot tested — bug is architecture-independent (missing NULL check, no pointer arithmetic or alignment dependency)

Note: This is a missing NULL check (CWE-476), not a memory layout or alignment issue. The crash path is identical regardless of pointer size, endianness, or alignment. All architectures that ship 389-ds-base with the deref plugin are affected.

Root Cause

File: ldap/servers/plugins/deref/deref.c:359-360 Function: deref_parse_ctrl_value() Verified against: 389-ds-base 3.2.0 (upstream HEAD) and 389-ds-base-3.1.4-6.fc42.x86_64 (Fedora 42 production)

The deref_parse_ctrl_value() function initializes a BER element from the LDAP control value but does not check the return value for NULL:

// deref.c:359-360 -- THE BUG
ber = ber_init((struct berval *)ctrlbv);           // can return NULL on OOM
for (tag = ber_first_element(ber, &len, &last);    // NULL deref if ber_init failed
     (tag != LBER_ERROR) && (tag != LBER_END_OF_SEQORSET);
     tag = ber_next_element(ber, &len, last)) {
    // ... BER parsing loop ...
}

The correct pattern exists at 21 other ber_init() call sites in the same codebase. For example, repl_controls.c:171:

// CORRECT: checks ber_init return inline
if (!BV_HAS_DATA(ctl_value) || (tmp_bere = ber_init(ctl_value)) == NULL) {
    rc = -1;
    goto loser;
}

The ber_init() function returns NULL when memory allocation fails internally (ber_alloc_t or ber_write failure). Under normal memory conditions, ber_init() will not fail on a well-formed control value. The OOM precondition is required.

Platform-specific crash behavior:

PlatformOpenLDAPSignalMechanism
RHEL 82.4.46SIGSEGVber_skip_tag writes to *(NULL+0x28) — dereference before NULL check
RHEL 9+ / Fedora 422.6.xSIGABRTassert(ber != NULL) in ber_tag_and_rest — defense-in-depth assert kills process

Both mechanisms terminate the ns-slapd process, causing a complete LDAP service outage.

Variant fix recommendation: The same NULL check should be added at 5 additional ber_init() call sites that lack it: vlv.c:1959, cb_controls.c:208, windows_private.c:1088, dyncerts.c:660, and dna.c:2171. These should be fixed under the same CVE for consistency and defense-in-depth.

Proof of Concept

PoC source code: CVE-2026-11788 on GitHub

Method: Automated GDB fault injection with raw-socket LDAP client. The PoC sends an anonymous LDAP SearchRequest with the deref control and GDB injects the ber_init() NULL return at the exact source line, producing a crash on the production binary with no manual intervention.

PoC Files

FileDescription
poc/014-deref-ber-init-null-deref.pyRaw-socket LDAP client (Python 3, no dependencies). Sends an anonymous SearchRequest with the deref control. Detects server crash via connection close/reset.
poc/014-gdb-commands-fc42.txtGDB command script for Fedora 42 / RHEL 9+ (requires 389-ds-base-debuginfo). Breaks at deref.c:360, nulls ber + $rax + $r12 to simulate OOM.
poc/014-gdb-commands-el7.txtGDB command script for CentOS 7 / RHEL 7 (no debuginfo needed). Breaks on ber_init, finishes, nulls $rax.
poc/014-poc-run.shSelf-contained runner. Auto-detects platform, starts ns-slapd under GDB, sends the deref search, captures crash backtrace, restarts slapd.
poc/014-deref-ber-init-null-deref.c(Superseded) Standalone logic simulation. Does not connect to a real server. Kept for reference.
  1. Install 389-ds-base with debuginfo and configure an instance with default settings:

    dnf install 389-ds-base 389-ds-base-debuginfo
    dscreate from-file /path/to/instance.inf
  2. Stop the running instance and run the automated PoC:

    ./poc/014-poc-run.sh <instance-name>

    The script auto-detects the platform (Fedora/RHEL 9+ vs CentOS/RHEL 7), starts ns-slapd under GDB with the appropriate fault injection script, sends the anonymous deref search via poc/014-deref-ber-init-null-deref.py, captures the crash backtrace, and restarts slapd cleanly.

  3. Expected result: Server crashes with SIGABRT (RHEL 9+/Fedora) or SIGSEGV (RHEL 8). Full backtrace captured by GDB:

    ns-slapd: decode.c:97: ber_tag_and_rest: Assertion `ber != NULL' failed.
    Thread 12 "ns-slapd" received signal SIGABRT, Aborted.
    
    #0  __pthread_kill_implementation () at libc.so.6
    #1  raise () at libc.so.6
    #2  abort () at libc.so.6
    #3  __assert_fail_base.cold () at libc.so.6
    #4  ber_tag_and_rest[cold] () at liblber.so.2     <- assert fires -> SIGABRT
    #5  ber_peek_element () at liblber.so.2
    #6  ber_skip_tag () at liblber.so.2
    #7  ber_first_element () at liblber.so.2
    #8  deref_parse_ctrl_value () at deref.c:360       <- THE BUG (ber = 0x0)
    #9  deref_pre_search () at deref.c:485
    #10 plugin_call_func () at libslapd.so.0
    #11 plugin_call_plugins () at libslapd.so.0
    #12 op_shared_search () at libslapd.so.0
    #13 do_search () at search.c:411                   <- anonymous search (no prior bind)

Manual Reproduction (Alternative)

  1. Start ns-slapd under GDB with the provided command script:

    gdb -batch -x poc/014-gdb-commands-fc42.txt \
        --args /usr/sbin/ns-slapd -D /etc/dirsrv/slapd-<inst> -i /run/dirsrv/slapd-<inst>.pid -d 0
  2. From another terminal, send an anonymous deref search:

    python3 poc/014-deref-ber-init-null-deref.py localhost 389

    Or via ldapsearch:

    ldapsearch -x -H ldap://localhost:389 -b "dc=example,dc=com" \
      -E 'deref=member:cn' "(objectClass=groupOfNames)"

Trigger Commands

# Automated (starts GDB, sends search, captures crash):
./poc/014-poc-run.sh test

# Manual -- Terminal 1 (start slapd under GDB):
gdb -batch -x poc/014-gdb-commands-fc42.txt \
    --args /usr/sbin/ns-slapd -D /etc/dirsrv/slapd-test -i /run/dirsrv/slapd-test.pid -d 0

# Manual -- Terminal 2 (send anonymous deref search):
python3 poc/014-deref-ber-init-null-deref.py localhost 389

Systems tested:

  • Fedora 42: 389-ds-base-3.1.4-6.fc42.x86_64 + openldap-2.6.13-1.fc42.x86_64 — SIGABRT confirmed
  • CentOS 7: 389-ds-base-1.3.11.1-5.el7_9.x86_64 + openldap-2.4.44-25.el7_9.x86_64 — crash confirmed in deref_pre_search

Note on trigger: The GDB injection simulates ber_init() returning NULL, which in production occurs under OOM conditions. The reproduction confirms the crash path is live end-to-end on production binaries and the missing NULL check causes a server-terminating crash when the OOM precondition is met.

Note on fault injection method: GDB (not LD_PRELOAD) is required because OpenLDAP 2.6+ uses versioned symbols (ber_init@@OPENLDAP_2.200). A naive LD_PRELOAD shim with an unversioned ber_init does not intercept calls from the deref plugin. GDB operates at the instruction level and bypasses this. On CentOS 7 / RHEL 7 (OpenLDAP 2.4.x, no symbol versioning), LD_PRELOAD interposition works but GDB is used for consistency across platforms.

In production, the OOM precondition can be achieved via unbounded memory allocation vulnerabilities (e.g., content sync plugin unbounded queue) or any other memory exhaustion scenario.

Impact

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

ComponentValueJustification
AVNetworkReachable over LDAP port (389/636) by any network client
ACHighRequires OOM precondition — ber_init() only returns NULL under memory exhaustion, not from crafted input. The attacker cannot directly trigger the NULL return via the control payload alone.
PRNoneAnonymous access enabled by default (nsslapd-allow-anonymous-access: on). No LDAP BIND required.
UINoneNo user interaction required
SUnchangedCrash confined to the ns-slapd process
CNoneNo information disclosure
INoneNo data modification
AHighComplete DoS of LDAP service — server crash confirmed on all platforms. Requires administrator restart.

Blast Radius

Primary impact: 389 Directory Server is the core LDAP server for Red Hat Identity Management (IdM/FreeIPA) and Red Hat Directory Server (RHDS). A crash of ns-slapd causes:

  • Complete LDAP service outage: All LDAP queries fail until the service is restarted by an administrator.
  • Authentication disruption: Systems using LDAP for authentication (SSSD, PAM, web applications) will fail to authenticate users.
  • Kerberos disruption: FreeIPA’s Kerberos KDC depends on the Directory Server for its principal database. A Directory Server crash can cascade to Kerberos authentication failures.
  • Replication disruption: Multi-supplier replication topologies may require manual intervention after a crash.
ComponentDependency on 389-ds-baseImpact of crash
FreeIPA / IdMCore data storeAuthentication, authorization, DNS, certificate services all fail
SSSDLDAP backendUser/group lookups fail; cached credentials may allow continued login for a limited time
Kerberos KDC (FreeIPA)Principal database in LDAPNew TGT requests fail; existing tickets remain valid until expiry
Dogtag PKI (FreeIPA)Certificate store in LDAPCertificate operations fail
Samba (RHDS)Directory backendDomain controller operations fail

Scale: 389-ds-base is deployed in every FreeIPA/IdM installation and every RHDS deployment. The deref plugin is enabled by default in all installations.

Comparable CVEs

CVEDescriptionCVSSComparison
CVE-2025-2487389-ds-base NULL deref via Modify DN operationModerateDifferent function (Modify DN), but same bug class (CWE-476) and same impact (DoS). Requires authentication (PR:L), unlike this finding (PR:N).
CVE-2022-2850389-ds-base NULL deref in Content Sync plugin6.5 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H)Same bug class. Requires authentication. AC:L because trigger is a crafted query (no OOM needed).
CVE-2017-2668389-ds-base NULL deref in LDAP bind6.5 (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)Same bug class, also pre-auth. AC:L because trigger is a crafted LDAP message (no OOM needed). This finding has AC:H due to the OOM requirement, resulting in a lower score.

Exploitation in the Wild

No evidence of exploitation in the wild found.

Searches performed across NVD, Red Hat Bugzilla, GitHub Issues (389ds/389-ds-base), Exploit-DB, GitHub Security Advisories, oss-security (openwall.com), and CISA KEV catalog. No matching CVE, bug, issue, or exploit found.

Note: PR #6253 (fanalyzer warnings fix, included in 3.1.1) addressed some NULL deref warnings but did not fix this specific bug — the 3.2.0 source still contains the vulnerable code.

Workaround

If patching is not immediately possible:

  1. Disable the deref plugin (most effective):

    dsconf <instance> plugin deref disable
    systemctl restart dirsrv@<instance>

    Impact: Clients using the LDAP deref control (OID 1.3.6.1.4.1.4203.666.5.16) will no longer receive dereferenced attributes in search results. Most LDAP clients do not use this control. This removes the attack surface entirely.

  2. Disable anonymous access (raises the bar from pre-auth to authenticated):

    dsconf <instance> config replace nsslapd-allow-anonymous-access=off
    systemctl restart dirsrv@<instance>

    Impact: Anonymous LDAP searches will be rejected. This does not eliminate the bug — an authenticated attacker can still trigger it — but it prevents unauthenticated exploitation.

  3. Configure memory limits (defense-in-depth):

    • Set nsslapd-maxbersize to restrict maximum BER element size.
    • Set nsslapd-conntablesize to limit concurrent connections.
    • Deploy in a cgroup with memory limits to prevent unbounded memory growth. These reduce the likelihood of OOM scenarios but do not fix the underlying bug.

Proposed Fix

// Add NULL check after ber_init in deref_parse_ctrl_value()
// Consistent with repl_controls.c:171, repl_controls.c:288-289,
// and acleffectiverights.c:199-203

ber = ber_init((struct berval *)ctrlbv);
if (ber == NULL) {                                  // ADD THIS CHECK
    *ldapcode = LDAP_OPERATIONS_ERROR;
    *ldaperrtext = "Failed to decode deref control value";
    return;
}
for (tag = ber_first_element(ber, &len, &last);
     (tag != LBER_ERROR) && (tag != LBER_END_OF_SEQORSET);
     tag = ber_next_element(ber, &len, last)) {
    // ... rest unchanged ...
}
ber_free(ber, 1);

The fix follows the exact pattern used at 21 other ber_init() call sites in the codebase. The return value types (*ldapcode as int, *ldaperrtext as const char *) and error code (LDAP_OPERATIONS_ERROR) are consistent with the existing error handling in the function.

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