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
| Package | Version | NVR | Status |
|---|---|---|---|
| 389-ds-base (upstream) | 1.2.6 through 3.2.0-dev | N/A (upstream source) | Vulnerable (confirmed — source review) |
| 389-ds-base (RHEL 8) | 1.4.x | 389-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.x | 389-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.x | 389-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.4 | 389-ds-base-3.1.4-6.fc42.x86_64 | Vulnerable (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
| Product | Version | 389-ds-base version | Status | Verification |
|---|---|---|---|---|
| 389 Directory Server (upstream) | all | 1.2.6 through 3.2.0 (HEAD) | Affected | Source 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.10 | 1.4.x (389-ds:1.4 module) | Affected | Ships 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.6 | 2.0.x-2.8.x | Affected | Ships 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.1 | 3.0.x-3.1.3 | Affected | All 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 11 | 11.x | 1.4.x | Affected | RHDS 11 ships same 389-ds-base as RHEL 8 (1.4.x). Same SIGSEGV crash path as RHEL 8. |
| Red Hat Directory Server 12 | 12.0-12.7 | 2.x | Affected | RHDS 12 ships same 389-ds-base as RHEL 9 AppStream. Same SIGABRT crash path as RHEL 9. |
| Fedora | 39-42 | 3.0.x-3.2.0 | Affected | Tracks 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 9 | 9 | 2.8.0 | Affected | Confirmed via dist-git; same code as upstream. |
| CentOS Stream 10 | 10 | 3.2.0 | Affected | Confirmed via dist-git; same code as upstream. |
Architecture Validation
| Architecture | Tested | Result |
|---|---|---|
| x86_64 | YES | Crash confirmed via GDB-controlled reproduction on Fedora 42 (389-ds-base-3.1.4-6.fc42.x86_64) — SIGABRT |
| aarch64 | NO | Not 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:
| Platform | OpenLDAP | Signal | Mechanism |
|---|---|---|---|
| RHEL 8 | 2.4.46 | SIGSEGV | ber_skip_tag writes to *(NULL+0x28) — dereference before NULL check |
| RHEL 9+ / Fedora 42 | 2.6.x | SIGABRT | assert(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
| File | Description |
|---|---|
poc/014-deref-ber-init-null-deref.py | Raw-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.txt | GDB 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.txt | GDB command script for CentOS 7 / RHEL 7 (no debuginfo needed). Breaks on ber_init, finishes, nulls $rax. |
poc/014-poc-run.sh | Self-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. |
Automated Reproduction (Recommended)
-
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 -
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. -
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)
-
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 -
From another terminal, send an anonymous deref search:
python3 poc/014-deref-ber-init-null-deref.py localhost 389Or 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 inderef_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)
| Component | Value | Justification |
|---|---|---|
| AV | Network | Reachable over LDAP port (389/636) by any network client |
| AC | High | Requires 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. |
| PR | None | Anonymous access enabled by default (nsslapd-allow-anonymous-access: on). No LDAP BIND required. |
| UI | None | No user interaction required |
| S | Unchanged | Crash confined to the ns-slapd process |
| C | None | No information disclosure |
| I | None | No data modification |
| A | High | Complete 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.
| Component | Dependency on 389-ds-base | Impact of crash |
|---|---|---|
| FreeIPA / IdM | Core data store | Authentication, authorization, DNS, certificate services all fail |
| SSSD | LDAP backend | User/group lookups fail; cached credentials may allow continued login for a limited time |
| Kerberos KDC (FreeIPA) | Principal database in LDAP | New TGT requests fail; existing tickets remain valid until expiry |
| Dogtag PKI (FreeIPA) | Certificate store in LDAP | Certificate operations fail |
| Samba (RHDS) | Directory backend | Domain 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
| CVE | Description | CVSS | Comparison |
|---|---|---|---|
| CVE-2025-2487 | 389-ds-base NULL deref via Modify DN operation | Moderate | Different function (Modify DN), but same bug class (CWE-476) and same impact (DoS). Requires authentication (PR:L), unlike this finding (PR:N). |
| CVE-2022-2850 | 389-ds-base NULL deref in Content Sync plugin | 6.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-2668 | 389-ds-base NULL deref in LDAP bind | 6.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:
-
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. -
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.
-
Configure memory limits (defense-in-depth):
- Set
nsslapd-maxbersizeto restrict maximum BER element size. - Set
nsslapd-conntablesizeto 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.
- Set
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
| 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
- CWE-476: NULL Pointer Dereference
- CVSS 3.1 Calculator
- 389-ds-base upstream repository
- CVE-2025-2487 — 389-ds-base NULL deref via Modify DN (related, same bug class)
- CVE-2022-2850 — 389-ds-base NULL deref in Content Sync plugin (related, same bug class)
- CVE-2017-2668 — 389-ds-base NULL deref via crafted LDAP bind (related, same bug class)
Credits
Discovered by Ian Murphy