Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Fix #17776 LDAP_OPT_X_TLS_REQUIRE_CERT can't be overridden #17940

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
remicollet wants to merge 1 commit into php:PHP-8.3 from remicollet:issue-ldap-newctx2

Conversation

Copy link
Member

@remicollet remicollet commented Feb 26, 2025

Alternative to #17939

robert-scheck reacted with thumbs up emoji
Copy link

This is my personal favorite given the IMHO strange OpenLDAP API.

Copy link
Member

@nielsdos nielsdos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably the better solution, at least better DX.

ext/ldap/ldap.c Outdated
}

#ifdef LDAP_OPT_X_TLS_NEWCTX
if (!memcmp(url, "ldaps:", 6)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that url can be NULL, because url = host and host is nullable.
Also I prefer if you use a normal string comparison rather than memcmp to avoid potential overreads. I don't know if that's possible (depends on the behaviour of ldap_is_ldap_url) but better safe than sorry and it wouldn't be really that much of a performance difference anyway.

Copy link
Member Author

@remicollet remicollet Feb 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link

robert-scheck commented Feb 26, 2025
edited
Loading

I am sorry, but as mentioned in #17776 (comment) I meanwhile noticed that this issue affects LDAPS (ldaps://) and STARTTLS (ldap://), so this pull request is unfortunately incomplete.

Most likely STARTTLS needs the following (untested!) adapted code block in PHP_FUNCTION(ldap_start_tls) after VERIFY_LDAP_LINK_CONNECTED(ld) but before calling ldap_start_tls_s(). In difference to LDAPS, STARTTLS starts with a plaintext connection and then upgrades the existing connection using STARTTLS, thus LDAP_OPT_X_TLS_NEWCTX should be applied to ld->link from my understanding. However, I'm not a C expert and also not sure about the OpenLDAP API...

#ifdef LDAP_OPT_X_TLS_NEWCTX
	int val = 0;
	/* ensure all pending TLS options are applied in a new context */
	if (ldap_set_option(ld->link, LDAP_OPT_X_TLS_NEWCTX, &val) != LDAP_OPT_SUCCESS) {
		php_error_docref(NULL, E_WARNING, "Could not create new security context");
	}
#endif

I also had a look to Postfix, there src/global/dict_ldap.c:580 uses quite similar code (they use the same code for LDAPS and STARTTLS, but their LDAP usage isn't directly comparable with PHP).

Copy link
Member Author

remicollet commented Feb 27, 2025
edited
Loading

@robert-scheck can you try this PR ?

It seems OK to me

Test LDAPS with LDAP_OPT_X_TLS_ALLOW
TLS certificate verification: Error, unable to get local issuer certificate
TLS certificate verification: Error, unable to verify the first certificate
LDAP bind succeeded (expected)
Test LDAPS with LDAP_OPT_X_TLS_DEMAND
TLS certificate verification: Error, unable to get local issuer certificate
TLS: can't connect: error:0A000086:SSL routines::certificate verify failed (unable to get local issuer certificate).
Warning: ldap_bind(): Unable to bind to server: Can't contact LDAP server in /work/build/phpmaster/bugldap.php on line 26
LDAP bind failed (expected)
Test STARTTLS with LDAP_OPT_X_TLS_ALLOW
TLS certificate verification: Error, unable to get local issuer certificate
TLS certificate verification: Error, unable to verify the first certificate
LDAP bind succeeded (expected)
Test STARTTLS with LDAP_OPT_X_TLS_DEMAND
TLS certificate verification: Error, unable to get local issuer certificate
TLS: can't connect: error:0A000086:SSL routines::certificate verify failed (unable to get local issuer certificate).
Warning: ldap_start_tls(): Unable to start TLS: Connect error in /work/build/phpmaster/bugldap.php on line 59
Warning: ldap_bind(): Unable to bind to server: Can't contact LDAP server in /work/build/phpmaster/bugldap.php on line 60
LDAP bind failed (expected)

Copy link
Member Author

Sadly, don't understand why CI is failing on ext/ldap/tests/ldap_start_tls_basic.phpt :(

Copy link

@robert-scheck can you try this PR ?

It seems OK to me

Yes, commit 9dde13a followed by commit 6466ed7 work for me here as well - thank you. For the record:

$ php /tmp/ldap.php
Test LDAPS with LDAP_OPT_X_TLS_ALLOW
TLS certificate verification: Error, unable to get local issuer certificate
TLS certificate verification: Error, unable to verify the first certificate
LDAP bind succeeded (expected)
Test LDAPS with LDAP_OPT_X_TLS_DEMAND
TLS certificate verification: Error, unable to get local issuer certificate
TLS: can't connect: error:0A000086:SSL routines::certificate verify failed (unable to get local issuer certificate).
PHP Warning: ldap_bind(): Unable to bind to server: Can't contact LDAP server in /tmp/ldap.php on line 26
LDAP bind failed (expected)
Test STARTTLS with LDAP_OPT_X_TLS_ALLOW
TLS certificate verification: Error, unable to get local issuer certificate
TLS certificate verification: Error, unable to verify the first certificate
LDAP bind succeeded (expected)
Test STARTTLS with LDAP_OPT_X_TLS_DEMAND
TLS certificate verification: Error, unable to get local issuer certificate
TLS: can't connect: error:0A000086:SSL routines::certificate verify failed (unable to get local issuer certificate).
PHP Warning: ldap_start_tls(): Unable to start TLS: Connect error in /tmp/ldap.php on line 59
PHP Warning: ldap_bind(): Unable to bind to server: Can't contact LDAP server in /tmp/ldap.php on line 60
LDAP bind failed (expected)
$ 

Copy link

Sadly, don't understand why CI is failing on ext/ldap/tests/ldap_start_tls_basic.phpt :(

Hard to say...if the CA of the certificate used for STARTTLS isn't part of the operating system trust store, I would expect it to fail. Or does the CI export something like LDAPTLS_REQCERT=... which influences the result? Local test using ldap_start_tls() leads to a failure here due to self-signed certificate, with and without this pull request applied (= as expected).

Copy link
Member Author

Trying without any change, only for CI

@remicollet remicollet marked this pull request as draft February 27, 2025 15:46
@remicollet remicollet force-pushed the issue-ldap-newctx2 branch 3 times, most recently from 4cb7cd4 to 2966b09 Compare February 28, 2025 07:39
Copy link
Member Author

Summary:

We now have 2 tests for ldaps and start_tls checking

  • default config (no option), cert is checked
  • option can be changed (what this issue was about)

Locally, both passes, default config was OK without this PR, running on a sane env/config

Problems in CI

  • start_tls default config behavior change (previous was not checked)
  • ldaps test fails (cert never checked despite option)

Other eyes welcome

May be classified security issue?

robert-scheck reacted with thumbs up emoji

@remicollet remicollet marked this pull request as ready for review February 28, 2025 08:19
Copy link

@robert-scheck robert-scheck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I'm not a qualified reviewer for the PHP project, this pull request addresses #17776 for me with the technically correct result on the PHP side: Refuses untrusted SSL certificate by default, overriding LDAP_OPT_X_TLS_REQUIRE_CERT works for both, STARTTLS (ldap://) and LDAPS (ldaps://), no change/impact on plaintext LDAP connections.

For me, the previous behaviour (without this pull request) qualifies as a security flaw, because being not able to override LDAP_OPT_X_TLS_REQUIRE_CERT using PHP and not receiving any error for the failed override (let alone not being able to set LDAP_OPT_X_TLS_NEWCTX directly) may lead to unexpected MITM when working with (multiple) trusted and untrusted LDAPS connections. I admit that working with two or more LDAP connections in a PHP script and different values for LDAP_OPT_X_TLS_REQUIRE_CERT might not be the typical everyday usage.

And I personally prefer this approach over LDAP_OPT_X_TLS_NEWCTX in #17939, because the OpenLDAP API seems to be somewhat strange here, and this change just leads to the correct result for PHP developers, similar like for other bindings (I don't know if there is any other library binding requiring an equivalent to LDAP_OPT_X_TLS_NEWCTX).

What I can not comment on is the PHP specific part.

Last but not least: Thank really you very much, @remicollet!

remicollet reacted with thumbs up emoji
Copy link
Member

@nielsdos nielsdos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix generally looks good to me , but no strong opinion on the target branch. A bit on the fence wrt the behaviour change. Not sure about security implications, I don't know enough about LDAP to judge that.

Copy link
Member Author

Copy link
Member

bukka commented Mar 4, 2025

I will need to investigate it as it's not immediately clear to me from a very quick look. I will try to find some time for it later today or tomorrow.

Copy link
Member

bukka commented Mar 6, 2025

I have been looking into this and currently feel that it's more an API limitation and mainly a missing constant. I can get why this change is done and it might make sense but I'm a bit worried about the behavior change. I need to look more to the openldap source to see how this is handled in terms of resetting other options. I think we should be a bit careful here so I set some time for ldap to my weekly plan and will be looking into this more. I think I will have a bit better idea before the next release branching and will send some update here.

Copy link

[...] but I'm a bit worried about the behavior change

Am I missing something? If you don't use LDAP_OPT_X_TLS_... at all, I don't see any behaviour change in my manual tests. And if you do, I still don't see any behaviour change with a typical single LDAP connection in a PHP script (or multiple ones to the same server if you just connect, disconnect, reconnect for whatever reason). The only "behaviour change" that I see is if you use multiple LDAP connections with different a certificate verification expectation per connection by applying ldap_set_option(), that it IMHO behaves now like it always should have done (and what other extensions do correctly already). Yes, I admit that if you wrote sloppy PHP code using multiple ldap_set_option() that worked accidentally before, a subsequent LDAP connection might fail now "unexpectedly". But that's why I call the old behaviour a security flaw (compare e.g with two HTTPS calls, you would expect that certificate verification takes place there as you set it, right?) and something that should to be mentioned in the release notes.

I am very sorry if we're talking about two different things, but I really would like to understand what you are worried about exactly.

Copy link
Member

bukka commented Mar 6, 2025

It's reseting the ctx (freeing the old one and creating a new one) that gets recreated from the currently saved options (combined with global ones) so there is a chance that there are some edge cases that we might be missing. I want to just analyse the impact of that to make sure that we are not going to miss anything and accidentaly break someones code.

robert-scheck reacted with thumbs up emoji

Copy link
Member Author

I don't see any behaviour change in my manual tests.

There is no behavior change on a modern distro (perhaps thanks to openldap 2.6 or good config).

The change was observed on CI (perhaps because of OpenLDAP 2.5 or poor config), where the only SSL test was failing, as cert check is now enabled by default, which makes sense.

Copy link
Member

bukka commented Mar 7, 2025

@remicollet So why do you even ask RM's if you are so confident that there is no behaviour change and no risk? :)

Copy link
Member

bukka commented Mar 7, 2025

If you feel that there is a security risk, you should open a new adivsory and not try to discuss it here - that should be always evaluated in private.

Copy link
Member

bukka commented Mar 7, 2025

It's also because security is not a question for RM but for the security team.

Copy link
Member

bukka commented Mar 7, 2025
edited
Loading

In terms of older versions, I can see that 2.4 is still in Ubuntu 20.04 which is likely still used (even though support ends next month or so but think there is an extended one. I see also that 2.5 is in Ubuntu 22.04 which will be even more in use. So it sounds we need to make sure that it works there fine as well.

Copy link
Member

bukka commented Mar 7, 2025
edited
Loading

I think I got it. I guess you were asking RM's because of the potential change on those versions. I will check the older versions in that case more. Thanks for the hint.

Copy link
Member

bukka commented Mar 7, 2025

As the security impact was mentioned here, I will then look into it as well and will open a new advisory if I feel, there is anything so it can be discussed further. But please try to not mention security related questions publicly in the future. As I said, if you have feeling that something might have security impact, just create a new advisory in the future.

robert-scheck reacted with thumbs up emoji

Copy link
Member Author

remicollet commented Mar 10, 2025
edited
Loading

Previous tests run on Fedora with openldap 2.6.8

New set of tests on RHEL-8.10 with openldap 2.4.46 and Fedora with manually installed openldap 2.5.19

In all cases:

  • no behavior change (cert is checked by default)
  • bug reproduced
  • bug fixed by this PR

Copy link
Member

bukka commented Mar 23, 2025
edited
Loading

So I have been looking through OpenLDAP source code (specifically tls2.c and tls_o.c). It took me a bit of time and didn't have time to test it but it seemed quite clear from the code - although I might be wrong somewhere.

I think I get why the API is the way it is. I guess the reason is primarily performance. It means not recreating global SSL_CTX for each connection which is really the purpose of SSL_CTX (possibility to re-use it). This makes sense specifically for loading the certs and keys. The idea is that the options (pretty much all the TLS ones - not just REQUIRE_CERT) need to be set before the first connection and then it cannot be changed by design. Specifically this is due this check: https://github.com/openldap/openldap/blob/72f184a0e3cb500ae4a8f81d212a5b6d9a6d1c91/libraries/libldap/tls2.c#L208-L209 . So you can see that this is definitely done on purpose and it makes some sense.

Theoretically there seems to be also a minor behaviour change in this regard if I'm not mistaken. Currently it is possible to set LDAP_OPT_X_TLS_CACERTFILE, then connect and then delete the file. This should then work for subsequent connections because it was loaded initially and ctx is still re-used. With this change it will no longer work because it will try to recreate the ctx but the file will no longer exist. I don't think this is really much of an issues in reality but maybe there is more.

Personally I see it more as a documentation issue and missing feature (in terms of not supported constant LDAP_OPT_X_TLS_REQUIRE_CERT). Basically the current contract is that the global options cannot be changed after the first connection. This is also how it works in OpenLDAP and I think we should keep the option to re-use the ctx between connections.

That said I'm actually wondering if there is a bug in not resetting the ctx between requests. I cannot really see anywhere in ldap ext code that there is any code that would reset the global context (e.g. in RINIT). If this is not there, then that should be, of course, addressed - e.g. by setting LDAP_OPT_X_TLS_CACERTFILE at the end of the request.

Copy link
Member Author

remicollet commented Mar 31, 2025
edited
Loading

Indeed, re-using CTX is an optimization which saves around 4ms for connection (from 12ms with new context to 8ms with re-used context, in my local test)

That said I'm actually wondering if there is a bug in not resetting the ctx between requests.

Yes, there is a bug, I just checked (using php-fpm configured for a single worker)
The context will be re-used for all connections during the process life (so LDAP_OPT_X_TLS_REQUIRE_CERT is not honoured)

Indeed, resetting context in RINIT may be a way to fix it.
BTW, I really think resetting the context before each connection is the safer way

  • optimization is very small (4ms)
  • and multiple connections in the same script is probably a rare use case (so optimization not really useful)

Copy link
Member Author

Indeed, resetting context in RINIT may be a way to fix it.

I don't find any API to do this, so as LDAP_OPT_X_TLS_NEWCTX creates the context, it must be called after the set option calls and before the connect.

@bukka can you please reconsider this PR

Copy link
Member

bukka commented Apr 6, 2025

Yeah so I think we should introduce global reset flag (bool variable) that would get set in RINIT to true and it would also get set to true if any global context setting is done. Then LDAP_OPT_X_TLS_NEWCTX would get set when creating connection if flag is set to true but after that, it would get set to false. So if there is a further connection created but there is no globals change, then the context would still get reused.

Copy link
Member Author

remicollet commented Apr 7, 2025
edited
Loading

Yeah so I think we should introduce global reset flag (bool variable)

I don't think it worth the complexity to save a few ms

But: Done

Copy link
Member

@bukka bukka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense. Just some minor things.

You are right that it's not probably worth it if you do few connections. But if you have long running application that creates many connection, then it could make a difference. It doesn't seem really that complex compare to some other things in php-src...

remicollet added a commit that referenced this pull request Apr 10, 2025
remicollet added a commit that referenced this pull request Apr 10, 2025
* PHP-8.3:
 NEWS for GH-17940
 Fix #17776 LDAP_OPT_X_TLS_REQUIRE_CERT can't be overridden
remicollet added a commit that referenced this pull request Apr 10, 2025
remicollet added a commit that referenced this pull request Apr 10, 2025
* PHP-8.4:
 NEWS for GH-17940
 NEWS for GH-17940
 Fix #17776 LDAP_OPT_X_TLS_REQUIRE_CERT can't be overridden
Copy link
Member Author

Thanks for the review

Merged in 8.3+ as 389de7c and 98fb27a

@remicollet remicollet deleted the issue-ldap-newctx2 branch April 10, 2025 15:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Reviewers

@bukka bukka bukka approved these changes

@nielsdos nielsdos nielsdos approved these changes

+1 more reviewer

@robert-scheck robert-scheck robert-scheck left review comments

Reviewers whose approvals may not affect merge requirements
Assignees
No one assigned
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /