- Package:
- src:openldap
- Source:
- src:openldap
- Submitter:
- Elliott Mitchell
- Date:
- 2024-05-21 04:54:03 UTC
- Severity:
- normal
- Tags:
Long story to finding this one. Trying to get LDAP setup on this
network. As a recent deployment it seemed appropriate to use IPv6.
From `nslcd` on clients I was getting the message:
nslcd[12345]: [1a2b3c] <group/member="root"> failed to bind to LDAP server ldaps://[fd12:3456:7890:abcd::3]/: Can't contact LDAP server: The TLS connection was non-properly terminated.: Resource temporarily unavailable
Running `nslcd` in debug mode failed to yield any additional useful
information.
Once I finally figured out `slapd`'s debug mode ('-h ldaps:/// ldapi:///'
is two arguments, the ldaps and ldapi are a single argument). I got
traces from `slapd`: (serial numbers filed off)
tls_read: want=5, got=5
0000: 16 03 01 01 8f
tls_read: want=399, got=399
0160: ............fd12
0170: :3456:7890:abcd:
0180: :3.-.........@.
TLS: can't accept: A disallowed SNI server name has been received..
connection_read(13): TLS accept failure error=-1 id=1005, closing
Further tracing of the error message appears to point to the function
`_gnutls_dnsname_is_valid()` in gnutls/lib/str.h. Seems libgnutls30 is
incompatible with numeric IPv6 addresses.
While IPv6-only hosts are presently uncommon, there is now quite a bit of
IPv6 traffic in many places. I think this is worthy of having a severity
of "critical" as "bookworm" may remain as "stable" past when there is
more IPv6 traffic than IPv4 traffic. For "trixie" this seems very
likely.
[...] Good morning, I guess you used the IPv6 address as either CN or Subject Alternative Name. Both take names, not IP addresses. There is a different field for IP addresses. gnutls-cli --port 636 fd12:3456:7890:abcd::3 will probably give more info. FWIW I have just generated a local test certificate with "IPAddress:" set to '::1' and things work for me as expected. cu Andreas
successfully established and I was left being able to type to `slapd`. Unfortunately that causes there to be 3 packages which could be the one responsible for the problem. Could be libgnutls30 as I originally suspected. Yet `slapd` and `nslcd` could also be responsible for the problem. The string "A disallowed SNI server name has been received." is found in `libgnutls.so.30`. The string "connection_read(%d): input error=%d id=%lu, closing." is found in `/usr/sbin/slapd`. Anything further is purely guesswork.
[...] [...] [...] [...] Hello, well you could post the complete output of gnutls-cli --port 636 fd12:3456:7890:abcd::3 perhaps even with -d10? I would reassign to openldap then if there are no obvious clues. cu Andreas
affects 1070033 nslcd quit `gnutls-cli` doesn't yield anything obvious. Problem is there are at least 3 packages where the bug could lurk: libgnutls30's API could indicate numeric addresses are legal somewhere, but not accept IPv6 addresses (something gets fed to _gnutls_dnsname_is_valid() which shouldn't be). I notice the libgnutls30 function _gnutls_dnsname_is_valid() will return true for "127.0.0.1". This function is almost certainly wrong as it accepts IPv4 addresses (which are not valid in DNS), but rejects IPv6 addresses. nslcd could be passing something which could be an IP address to the wrong part of the libgnutls30 API. nslcd might also be sending an IP address in LDAP somewhere it is required to send a hostname. slapd could be passing something which could be an IP address to the wrong part of the libgnutls30 API. slapd might also be assuming something in LDAP is a hostname when it is valid to be an IP address. Right now _gnutls_dnsname_is_valid() seems highly suspect.
[...] [...] Hello, Could you please post the requested output, although there are no obvious clues there to your eyes? TIA, cu Andreas
Problem is that provides rather a lot of data about this network setup. The quantity of information is enough for me to be rather uncomfortable with providing it via public channel. I did get the connection to proceed further than before though. If I add the IPv6 address of the LDAP server to /etc/hosts, and then use the hostname instead of IPv6 address for the uri line of /etc/nslcd.conf things get further (I believe over IPv6, but I haven't satisfactorily verified this). This suggests #1070033 is either in libgnutls30 or slapd. The issue could be slapd is passing an IPv6 address to a portion of libgnutls30's API which requires a hostname. The issue could be libgnutls30 rejects IPv6 addresses in some place(s) where they should be valid by the API. I notice the `_gnutls_dnsname_is_valid()` function in gnutls28-3.8.5/lib/str.h accepts IPv4 addresses (which are NOT valid in DNS), but rejects IPv6 addresses.
Then I look deeper and find RFC 6066 (https://www.rfc-editor.org/rfc/rfc6066), page 7: Literal IPv4 and IPv6 addresses are not permitted in "HostName". This suggests there are at least 2, possibly 3 or more bugs. #1 RFC 6066 says neither are legal, yet _gnutls_dnsname_is_valid() accepts IPv4 addresses (including the 32-bit integer version), but rejects IPv6 addresses. This sort of inconsistency leads to security breaches. #2 The gnutls library uses the SNI extension without checking whether it was passed a literal addresses. #3 nslcd always passes the host string provided to its "uri" configuration setting to the gnutls API without checking whether it is a literal address. #1 is definitely a bug present in the libgnutls30 package. At least one of #2 and #3 is definitely a bug, but both may very well be bugs. Seems better to check in the library as it could effect multiple programs using the library.
[...] [...] Hello, At a very bare level an IPv4 address is a valid DNS name (alnum, dashes, and dots), an IPv6 adress is not. That is what gnutls is checking here. Afaict it is a short-cut to save more expensive processing for obvious errors. gnutls_session_get_verify_cert_status() (with gnutls_session_set_verify_cert() set correctly) or gnutls_x509_crt_check_hostname()/gnutls_certificate_verify_peers3() does more elaborate stuff on the data, gnutls_certificate_verify_peers2() requires a separate gnutls_x509_crt_check_hostname(). cu Andreas
No, there isn't any IPv4 address which is a valid DNS name. No top-level domain consists purely of decimal digits, whereas IPv4 addresses consist of purely decimal digits. In fact I don't believe there are any top-level domains which have even a single decimal digit in them. Which seems to argue the more urgent issue is _gnutls_server_name_send_params() needs to do checking of the provided server hostname before sending it as SNI. I've got an initial implementation of this here, but I'm left wondering how far verification should go.
[...] Hello, which is totally irrelevant if my reading (quoted below) that this is not a policy check but a performance optimization is correct. Why is this urgent or even relevant? Certificate checking (client-side) will not accept IP adresses as SNI field. cu Andreas
Most recent change to the line, commit 71d921edc4:
Add GNUTLS_E_RECEIVED_DISALLOWED_NAME for illegal SNI names
An illegal/disallowed SNI server name previously generated
the misleading message "An illegal parameter has been received.".
This commit changes it to
"A disallowed SNI server name has been received.".
That commit message clearly indicates the author was thinking of it as a
policy check.
Not relevant. If the certificate comes from a local file, it is assumed
trusted. If the certificate comes from the server, then it is only
available *after* connection and the SNI has already been sent.
The issue is libgnutls's API requires providing the library with the
server being connected to. libgnutls then assumes the provided server
can be used for SNI, which is untrue (in this case IP addresses violate
RFC 6066).
[...] [...] Hello, You seem to argue that it is major problem for a gnutls client to *send* e.g. "127.0.0.1" as SNI. My point is that this is not a problem but at most uncomely since client-side certificate verification will fail. Even for a trusted certificate name checking is done (if gnutls is correctly used). And this will not succeed if the CN or SAN is an IP address. (I have tried with test certificates and gnutls-cli/-serv. My testing might be flawed of course.) cu Andreas
This is purely hypothetical since this case isn't being observed. What #1070033 is about is, a program was configured to directly connect to a server via IPv6. This address was provided to libgnutls. libgnutls sent the provided address to the server as SNI without verifying it was valid for SNI. The usual approach is be conservative in what you send, but liberal in what you accept. This means libgnutls needs to check whether what is provided is acceptable before sending it, but the server side could allow an IP address which violates RFC 6066. `gnutls-cli` is a very poor simulcrum for this case. `gnutls-cli` does lots of checking which specialized clients may skip. `gnutls-cli` also assumes name service is fully available. Whereas `nslcd` cannot rely on name service being operational as it may provide name service.
[...] Hello, Let's assume a) _gnutls_server_name_send_params() was changed to reject e.g. "127.0.0.1"[1] and b) this stopped libgnutls from sending "127.0.0.1" to the server as SNI. How would this help you, or how is this related to this bug report? In this bug report perhaps an IPv6 address was used which is already rejected by _gnutls_server_name_send_params(). cu Andreas
This is not something I proposed and indeed this wouldn't help me. _gnutls_server_name_recv_params() does some rough filtering which catches IPv6 addresses, but not IPv4 addresses. _gnutls_server_name_send_params() does NO filtering and thus sends both IPv4 and IPv6 addresses. libgnutls is being conservative in what it accepts, but liberal in what it sends. This breaks interoperability.
Seems there were two bugs in #1070033. The part for OpenLDAP is pretty simple. When detecting an IPv6 address (via ':' in the string), the function `ldap_int_tls_connect()` triggers a `break;`, but this requires `numeric=1` to still be in effect. Since IPv6 addresses are hexadecimal, this isn't always true. Patch attached. Given how small it is, any license acceptable to the Debian project is acceptable to me. I'll let the maintainer forward it to the OpenLDAP project.
Hi Elliott, thank you for investigating this issue and contributing a patch. However, I tested your patch, and I'm not sure it's correct. If the IPv6 address contains a letter a-f before the first colon, I think the code you changed is never reached. On seeing the first non-digit, we break the loop with numeric=0, and never reach the colon. Have I missed something? I would appreciate if you would pursue this issue upstream. If the fix needs further review or discussion with the upstream developers, I'd really rather not be a middleman in that conversation. Thank you, Ryan
--On Monday, May 20, 2024 1:46 PM -0700 Ryan Tandy <ryan@nardis.ca> wrote: [snip] Upstream generally does not accept 3rd party patch contributions, so asking debian to contribute it wil likely result in it not being accepted. So it's better to work directly with the OpenLDAP project. I'd start by filing an issue in the issue tracker if one doesn't already exist: https://bugs.openldap.org and then apply for a gitlab account with the OpenLDAP project: https://git.openldap.org After the account is approved, you can open a PR to have your patch evaluated. Regards, Quanah Release Engineer, OpenLDAP project.
No, you haven't missed something. %-) Turns out I goofed when reading
the loop. Indeed the `if(!isdigit(*c)) {` needs to have the `break;`
removed too, then it will work.
The person writing the loop was thinking of the most commonly used block
of IPv6 addresses which start with "2001:". Yet IPv6 is hexadecimal and
"fd00:/8" is part of a validly used block.
Debian policy for maintainers is they're required to take care of pushing
issues upstream. I didn't want to deal with the OpenLDAP bug tracker and
those steps, so pushing to the Debian project seemed handiest.
--On Monday, May 20, 2024 3:45 PM -0700 Elliott Mitchell <ehem+debian@m5p.com> wrote: Side note - I did raise this issue with the rest of the OpenLDAP project, and Howard noted: "DNS names are required to begin with a letter. RFC 1035, sec 2.3.1. The fact that gnutls allows names that are all numeric is certainly their bug". So I guess two bugs here. Regards, Quanah
According to what I found, that requirement was removed. This doesn't invalidate the fact that no top-level domain consists exclusively of numbers (in fact I'm pretty sure none have any numbers). I'm proposing checking only for nul-characters and passing everything else through. Principle being anything handling SNI must handle the case of a string which fails to match a known entry. If a server program chose to honor strings which violate RFC 6066, GnuTLS doesn't need to get in the way of that. Simply terminating the connection really isn't to helpful (it could simply be a bug).