https://bugs.openldap.org/show_bug.cgi?id=10489
Issue ID: 10489 Summary: memory issue in liblber Product: OpenLDAP Version: 2.6.9 Hardware: All OS: All Status: UNCONFIRMED Keywords: needs_review Severity: normal Priority: --- Component: libraries Assignee: bugs@openldap.org Reporter: yutengsun1997@gmail.com Target Milestone: ---
Dear OpenLDAP Security Team,
I reported a potential security issue two weeks ago, but have not received a response. I am writing to confirm whether I emailed the right place. I can attach the email content if required. Looking forward to your reply. Many thanks.
Regards, Yt
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #1 from Howard Chu hyc@openldap.org --- (In reply to yutengsun1997 from comment #0)
Dear OpenLDAP Security Team,
I reported a potential security issue two weeks ago, but have not received a response. I am writing to confirm whether I emailed the right place. I can attach the email content if required. Looking forward to your reply. Many thanks.
Regards, Yt
What was the ITS# for your issue? You would have received a copy of the issue emailed back to you.
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #2 from yutengsun1997@gmail.com --- Actually, I did not receive anything. I reported it to project@openldap.org. Is this the right place to report it? If not, could you please give me an email address? Alternatively, can I download and attach the original email here?
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #3 from Howard Chu hyc@openldap.org --- (In reply to yutengsun1997 from comment #2)
Actually, I did not receive anything. I reported it to project@openldap.org. Is this the right place to report it? If not, could you please give me an email address? Alternatively, can I download and attach the original email here?
We don't take bug reports via email. We only take them as issues submitted here.
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #4 from yutengsun1997@gmail.com --- Got it, here is the origial email content.
Subject: Possible memory issue in liblber: ber_skip_tag() / ber_skip_element() / ber_skip_data() — unchecked ber_ptr dereference
Dear OpenLDAP Security Team,
I hope this message finds you well. I have been doing some security research on BER/DER parsing libraries and came across a potential memory issue in liblber that I would appreciate your assessment on.
## Summary
Three functions in liblber dereference `ber->ber_ptr` to cache the next tag byte without verifying that `ber_ptr < ber_end`. When the current element is the last one in the buffer (or when parsing fails), `ber_ptr` may point at or past `ber_end`, resulting in a 1-byte out-of-bounds read.
I wanted to ask: would you consider this a memory safety issue worth addressing?
## Affected Code
**OpenLDAP 2.6.9** (and likely all prior versions). Three locations:
### 1. `ber_skip_tag()` — libraries/liblber/decode.c:274-275
```c ber->ber_ptr = bv.bv_val; ber->ber_tag = *(unsigned char *) ber->ber_ptr; // unconditional dereference ```
This runs regardless of whether `ber_peek_element()` succeeded or failed. Unlike `ber_skip_element()`, there is no `if (tag != LBER_DEFAULT)` guard, so the dereference occurs even on parse failure.
### 2. `ber_skip_element()` — libraries/liblber/decode.c:230-231
```c ber->ber_ptr = bv.bv_val + bv.bv_len; ber->ber_tag = *(unsigned char *) ber->ber_ptr; // OOB when last element ```
When the parsed element is the last one in the buffer, `ber_ptr` is advanced to exactly `ber_end`, and the read goes 1 byte past the buffer.
### 3. `ber_skip_data()` — libraries/liblber/io.c:62-63
```c ber->ber_ptr += actuallen; ber->ber_tag = *(unsigned char *)ber->ber_ptr; // same pattern ```
## Why ber_init() masks the issue
I noticed that `ber_init()` allocates `bv_len + 1` bytes and NUL-terminates the copy, so the out-of-bounds read hits a sentinel byte within the allocation. This effectively masks the issue in many common code paths.
However, `ber_init2()` uses the caller's buffer in-place without extra allocation, which makes the out-of-bounds read a real access past the buffer boundary.
## Reachability via ber_init2()
From what I can see, `ber_init2()` is called in a number of places that process incoming data, including:
- `servers/slapd/cancel.c` — extended operation request data - `servers/slapd/passwd.c` — password modify request data - `servers/slapd/controls.c` — LDAP control values - `servers/slapd/schema_init.c` — DER-encoded attribute values - `servers/lloadd/bind.c`, `servers/lloadd/client.c` — load balancer data - `libraries/libldap/result.c` — client-side server response parsing - `libraries/libldap/tls_g.c:504` — X.509 certificate DN extraction - Various overlays (syncprov, sssvlv, deref, valsort, etc.)
## Reproduction
A minimal example using `ber_init2()`, compiled with AddressSanitizer:
```c #include <lber.h> #include <stdint.h>
int main(void) { // A complete NULL element: tag=0x05, length=0x00 uint8_t buf[2] = {0x05, 0x00};
BerElement *ber = ber_alloc_t(0); struct berval bv; bv.bv_val = (char *)buf; bv.bv_len = sizeof(buf); ber_init2(ber, &bv, 0);
// ber_skip_tag() advances ber_ptr to buf[2] (past end), // then dereferences it to cache ber_tag. ber_len_t len = 0; ber_skip_tag(ber, &len);
ber_free(ber, 0); return 0; } ```
Compile and run: ```bash clang -g -O1 -fsanitize=address,undefined -I<openldap>/include \ -DLBER_LIBRARY decode.c io.c memory.c options.c sockbuf.c \ bprint.c debug.c assert.c encode.c stdio.c test.c -o test ./test ```
ASan reports: ``` ERROR: AddressSanitizer: stack-buffer-overflow on address ... READ of size 1 at ... #0 ... in ber_skip_tag .../decode.c:275 ```
## Suggested Fix
A bounds check before each `ber_ptr` dereference would address this:
```c if (ber->ber_ptr < ber->ber_end) ber->ber_tag = *(unsigned char *) ber->ber_ptr; else ber->ber_tag = LBER_DEFAULT; ```
Additionally, `ber_skip_tag()` could benefit from the same `if (tag != LBER_DEFAULT)` guard that `ber_skip_element()` already has, to avoid the dereference on parse failure.
## Notes
- I understand that `ber_init()` mitigates this in practice for many code paths. I am reporting this primarily because `ber_init2()` is a public API and is used internally in places that handle incoming data. - I have only tested this on OpenLDAP 2.6.9 but the code pattern appears to have been present for a long time.
Thank you very much for your time and for maintaining OpenLDAP. I would be happy to provide any additional information or testing if that would be helpful.
Best regards.
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #5 from Howard Chu hyc@openldap.org --- Thanks for the report. This code appears to have been introduced in 2009, commit bc20500e629e15f2bc937b4952ee1395872e3d4d
Fixed now in master 14f3e73301f84382356aacb5a22668e47e81a74a
In practice there doesn't appear to be any impact on slapd.
- `servers/slapd/cancel.c` — extended operation request data - `servers/slapd/passwd.c` — password modify request data - `servers/slapd/controls.c` — LDAP control values - `servers/slapd/schema_init.c` — DER-encoded attribute values
All of the buffers being referenced here came from liblber originally, and thus have an extra trailing NUL byte anyway.
https://bugs.openldap.org/show_bug.cgi?id=10489
Howard Chu hyc@openldap.org changed:
What |Removed |Added ---------------------------------------------------------------------------- Resolution|--- |TEST Target Milestone|--- |2.6.14 Status|UNCONFIRMED |RESOLVED Keywords|needs_review |
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #6 from Howard Chu hyc@openldap.org --- To be explicit: ber_get_next() is used to read all incoming data, and always allocates one extra byte for incoming messages. This ensures that we can return values directly from the message buffer (zero additional copies) and the caller can always NUL-terminate the returned value if desired.
So this only potentially affects third party code that doesn't allocate message buffers accordingly. There's zero impact to OpenLDAP code.
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #7 from yutengsun1997@gmail.com --- Thanks for your quick reply.
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #8 from yutengsun1997@gmail.com --- Thanks for explaining the `ber_get_next() + 1` allocation — that makes sense for OpenLDAP's own code paths (I just go through openldap's code, it should be true and will not influence OpenLDAP itself).
However: I wanted to ask if you'd be open to assigning a CVE for this. My reasoning:
ber_init2() is a public API, and the +1 byte requirement isn't documented. Third-party callers using exact-sized buffers would hit the OOB read. The ber_skip_tag() error path dereferences ber_ptr unconditionally (missing the if (tag != LBER_DEFAULT) guard that ber_skip_element() has), which is a separate issue from buffer sizing. I understand the impact to OpenLDAP itself is not big, and I am okay with a not that high severity rating. Would you be able to help assign a CVE for this? It would be helpful to me. Happy to assist with any information needed. Please let me know your opinion.
Thanks!
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #9 from Howard Chu hyc@openldap.org --- I'm quite reluctant to issue a CVE for this since there is no known instance of an application that's affected by it. It's really uncommon for 3rd party applications to use liblber directly, usually the use libldap and won't be exposed to the issue anyway. If you know of any application calling liblber directly, that is vulnerable, then we can discuss it further.
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #10 from Howard Chu hyc@openldap.org --- Currently I'm inclined to revert the patch I just applied, and instead just document that ber_init2() requires its buffer to be over-allocated by 1 byte.
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #11 from yutengsun1997@gmail.com --- Hi,
I spent a lot of time to check the call graph of OpenLDAP, and found the `+1` guarantee does NOT cover all code path.
The `ber_get_next() +1` sentinel applies only to buffers from incoming LDAP messages. But `tlsg_x509_cert_dn()` in `libldap/tls_g.c:532` receives its buffer from a completely different allocator: ``` ldap_pvt_tls_get_peer_dn() ← public libldap API → tlsg_session_peer_dn() bv.bv_val = peer_cert_list->data ← gnutls_malloc(data_size) bv.bv_len = peer_cert_list->size ← exactly data_size, NO +1 tlsg_x509_cert_dn(&bv, ..., 1) ber_init2(ber, cert, LBER_USE_DER) ← uses caller's buffer directly ber_skip_tag() → *(unsigned char*)ber_ptr ← OOB when ber_ptr==ber_end
``` Proven in GnuTLS source — `_gnutls_set_datum()` at `datum.c:42`: ``` unsigned char *m = gnutls_malloc(data_size); // exactly data_size, no +1 dat->data = m; // ← no + 1 dat->size = data_size; // ← no + 1 ``` This is OpenLDAP's own code calling a third-party allocator that follows no such internal convention. The `+1` defence relies on an undocumented internal convention that is invisible to any external allocator.
Please let me know if I made any mistakes. And I'd like to see your comments. Many Thanks.
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #12 from Howard Chu hyc@openldap.org --- tlsg_x509_cert_dn() is only called with a certificate that GnuTLS has already accepted, so it is already known to be valid. Since this function only returns a DN from the certificate, and there is more content in a certificate following the DN, there is no possibility of over-reading past the end of the certificate buffer.
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #13 from Quanah Gibson-Mount quanah@openldap.org --- head:
• 14f3e733 by Howard Chu at 2026-04-09T17:41:03+01:00 ITS#10489 liblber: fix potential 1-byte overread
RE26:
• 4ad04879 by Howard Chu at 2026-04-10T22:49:08+00:00 ITS#10489 liblber: fix potential 1-byte overread
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #14 from yutengsun1997@gmail.com --- Thank you for the clarification and for taking the time to explain the rationale.
You mentioned there is no known third-party application using liblber directly that is affected, which challenged me to verify this. I found a concrete counterexample: FreeIPA, a widely deployed enterprise identity management system.
In daemons/ipa-slapi-plugins/ipa-cldap/ipa_cldap_worker.c, FreeIPA uses ber_init2() strictly to parse unauthenticated Connectionless LDAP (CLDAP) UDP packets, passing the buffer exactly as received from the network, with no extra +1 byte allocated: ```C // ipa_cldap.h struct ipa_cldap_req { char dgram[MAX_DG_SIZE]; // MAX_DG_SIZE = 4096, fixed-size network buffer size_t dgsize; // bytes actually received // ... };
// ipa_cldap_worker.c bv.bv_val = req->dgram; // exact-size buffer, NO +1 bv.bv_len = req->dgsize; ber_init2(be, &bv, 0); // ... ber_skip_tag(be, &len);
```
I wrote a small PoC mimicking this exact memory logic to prove it. If an attacker sends a simply crafted datagram (e.g., a 2-byte empty sequence 0x30 0x00), recvfrom sets dgsize = 2. The ber_skip_tag() function unconditionally dereferences ber_ptr, reading dgram[2], which is 1 byte out of bounds of the logically provided bv_len.
To be completely objective: in this specific FreeIPA build, this OOB read will not trigger a Segfault or an ASan crash. This is strictly due to "bug masking by memory layout" — because dgram is a static array enclosed within the calloc'd struct and is immediately followed by dgsize, the OOB read quietly fetches the first byte of dgsize. However, this definitively proves that the vulnerable pattern exists. If FreeIPA or any other application had chosen the standard idiom of using char *dgram = malloc(dgsize);, this would be an instantly exploitable Heap-Buffer-Overflow against a network-facing daemon.
I would also like to add two minor points for your consideration: 1. Closed-source Prevalence: While it might be uncommon to find direct liblber usage in open-source clients, LDAP is overwhelmingly used in proprietary enterprise directory solutions, legacy identity management gateways, and customized network appliances. These closed-source applications may very well be using ber_init2 dynamically, completely unaware of the undocumented +1 sentinel requirement. 2. My Background & Request: To be transparent, I am not primarily doing manual source auditing. I found this issue systematically while building infrastructure to measure and fuzz DER/BER parsers at scale. On a personal level, having a CVE assigned would be beneficial for my academic/research work. However, I want to be respectful of your time and your project's security policies. If you strongly feel this does not cross the threshold for a CVE from the OpenLDAP project's perspective, I will gladly respect that decision and will simply note it as "Reported to vendor; <fixed>" (<> means it might not appear) in my research evaluation.
I will leave the final assessment in your hands. Thank you again for the fantastic work on OpenLDAP and for reviewing my findings!
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #15 from Ondřej Kuzník ondra@mistotebe.net --- On Sat, Apr 11, 2026 at 09:00:51AM +0000, openldap-its@openldap.org wrote:
To be completely objective: in this specific FreeIPA build, this OOB read will not trigger a Segfault or an ASan crash. This is strictly due to "bug masking by memory layout" — because dgram is a static array enclosed within the calloc'd struct and is immediately followed by dgsize, the OOB read quietly fetches the first byte of dgsize. However, this definitively proves that the vulnerable pattern exists. If FreeIPA or any other application had chosen the standard idiom of using char *dgram = malloc(dgsize);, this would be an instantly exploitable Heap-Buffer-Overflow against a network-facing daemon.
You are saying this one byte overread is exploitable, that is quite the claim, can you explain how this capability could be "exploitable" to gain anything? In my view an application would need to be extremely reckless in the first place to allow crafting this into a widget for something like attacker controlled reads...
I would also like to add two minor points for your consideration:
- Closed-source Prevalence: While it might be uncommon to find direct liblber
usage in open-source clients, LDAP is overwhelmingly used in proprietary enterprise directory solutions, legacy identity management gateways, and customized network appliances. These closed-source applications may very well be using ber_init2 dynamically, completely unaware of the undocumented +1 sentinel requirement.
And why I'm +1 to keep the fix in.
- My Background & Request: To be transparent, I am not primarily doing manual
source auditing. I found this issue systematically while building infrastructure to measure and fuzz DER/BER parsers at scale. On a personal level, having a CVE assigned would be beneficial for my academic/research work. However, I want to be respectful of your time and your project's security policies. If you strongly feel this does not cross the threshold for a CVE from the OpenLDAP project's perspective, I will gladly respect that decision and will simply note it as "Reported to vendor; <fixed>" (<> means it might not appear) in my research evaluation.
I will leave the final assessment in your hands. Thank you again for the fantastic work on OpenLDAP and for reviewing my findings!
In the beginning you mentioned this is a safety issue (bug), not a security issue (vulnerability). On that I agree, and you would have to show why it needs to be reclassified or have any of our downstreams weigh in.
We are grateful for every valid issue report and happy to discuss their potential ramifications within reason. But at this point it is up to you to show why it is serious enough to warrant a CVE, which I believe you acknowledged is contrary to how the project handles safety issues and only benefits you.
Thanks,
https://bugs.openldap.org/show_bug.cgi?id=10489
--- Comment #16 from yutengsun1997@gmail.com --- Thank you for your honest reply. Here I am writing this NOT to ask for a CVE, but to clear up some misunderstandings which should be caused by my poor writing.
First, I want to apologize for using the word "exploitable" in my last email. TBH, I am not very good at writing and presentation in English, so I used a local LLM to polish my text. It seems the tool changed my original meaning and made my claim sound much more aggressive than I intended.
From the beginning, my focus was only on testing how open-source lib's APIs handling ASN.1 parsing. That's why I only looked at `ber_init()` and `ber_init2()` in OpenLDAP. Also, because I only looked at the API level, I thought I found a robustness problem, so I reported it and asked about a CVE. (Thank you to Chu for explaining the `alloc + 1` defense, and thank you to OpenLDAP team for acknowledging the bug.)
Now I understand that your security policy looks at the whole OpenLDAP project, not just the library API by itself. I completely respect and agree with your rule. I really do not want to be annoying about the CVE. I will simply write "reported, fixed" in my work. I won't make it sound worse than it is. Having a CVE is good for my paper (to convince others), but I am still very thankful for your help even without one.
Regarding the FreeIPA example: my only point was to show that keeping the API robust is would be better. I felt that just rolling back the fix wouldn't really solve the problem. I am very happy that we agree on keeping the fix to make the code safer.
I hope I have explained my thoughts clearly this time. Thank you again for your time and your great work on OpenLDAP!