Problem/Motivation

Drupal prefers openssl_random_pseudo_bytes() if available in Crypt::randomBytes() in 8.0.x or drupal_random_bytes() in 7.x and 6.x.

PHP used the wrong method in the openssl library now fixed in 5.6.12, 5.5.28, 5.4.44 see: https://bugs.php.net/bug.php?id=70014 but it is NOT classified as a security hole so backports are to older (ie distro) versions are less likely. Just because PHP didn't declare this a security hole doesn't mean it is not. But also read on for other scenarios where this class creates a security hole.

The returned pseudo-random bytes were NOT necessarily cryptographically secure.

Proposed resolution

Add random_compat v2.0.2 to core + use it for Crypt::randomBytes()
Adding v2 should be ok here because in drupal 7.x this includes a fallback.
(so not a concern like in #2763787: Upgrade random_compat to latest version which reverted the upgrade to v2.0.2 in drupal 8.2.x and 8.3.x)

Remaining tasks

review

User interface changes

n/a

API changes

n/a

Data model changes

n/a

RC phase evaluation

Reference: https://www.drupal.org/core/d8-allowed-changes
Issue category Bug because the Crypt class doesn't do what the docblock says, and what the docblock says reflects how the class is actually used in Drupal.
Issue priority Critical because:
  • The class docblock claims to return cryptographically secure random data, which is not the case on:
    • PHP installations with versions lower than 5.6.12, 5.5.28, 5.4.44 with the openssl extension enabled
    • PHP installations on Windows, which are vulnerable to a situation where an attacker writes static data to C:\dev\urandom, which would make subsequent reads (i.e. from /dev/urandom in Crypt::randomBytes()) return nonrandom data
    • PHP installations which don't have ext_openssl and can't read from /dev/urandom, which makes Drupal fall back to a userspace "random" byte generator, which is not a good thing to do, especially if the class claims to produce cryptographically secure random bytes
  • The class claims to use the best possible source of cryptographically secure random bytes, which is not the case in all the situations listed above in addition to installations that have ext_sodium installed or PHP7 installations, which have random_bytes().
  • The PHP bug wasn't flagged as a security hole, so no CVE was issued, which means that the fix is less likely to be backported to older distro versions, which makes it more important that we work around it as we do with other PHP bugs/undesirable behaviors (drupal_[php built-in function] in D7 and whatever they were converted to in D8)
Disruption None. Everything should work exactly the same, except with actual cryptographically secure pseudorandom bytes
CommentFileSizeAuthor
#225 2550519-225.patch1.55 KBmfb
#220 random_bytes-2550519-D7-144-reroll-220.patch71.46 KBsinduri
#219 random_bytes-2550519-D7-144-reroll-219.patch71.14 KBsinduri
#218 random_bytes-2550519-D7-144-reroll-218.patch71.34 KBNikolay Shapovalov
#204 interdiff.txt1.15 KBAyesh
#204 2550519-204.patch74.08 KBAyesh
#195 increment-2550519-195.txt452 bytespwolanin
#195 2550519-195.patch73.85 KBpwolanin
#193 increment-2550519-193.txt3.93 KBpwolanin
#193 2550519-193.patch73.84 KBpwolanin
#181 interdiff_2550519-177-181-do-not-test.diff2.54 KBHeine
#181 2550519-181.patch73.61 KBHeine
#177 interdiff_2550519-171-177-do-not-test.diff1.49 KBHeine
#177 2550519-177.patch72.41 KBHeine
#171 increment-2550519-171.txt1.41 KBpwolanin
#171 2550519-171.patch72.3 KBpwolanin
#168 increment-2550519-168.txt2.43 KBpwolanin
#168 2550519-168.patch72.97 KBpwolanin
#166 2550519-166.patch72.91 KBYesCT
#166 interdiff.2550519.156.166.txt1.48 KBYesCT
#156 increment-2550519-156.txt1.34 KBpwolanin
#156 2550519-156.patch72.81 KBpwolanin
#151 increment-2550519-151.txt1.16 KBpwolanin
#151 2550519-151.patch71.46 KBpwolanin
#148 increment-2550519-147.txt1.92 KBpwolanin
#148 2550519-147.patch1.3 KBpwolanin
#145 interdiff.txt641 bytesklausi
#144 random_bytes-2550519-D7-144.patch71.33 KBklausi
#133 interdiff.txt3.47 KBklausi
#133 random_bytes-2550519-D7-132.patch70.71 KBklausi
#131 random_bytes-2550519-D7.patch74.16 KBklausi
#109 test8.loc_.html_.zip3.67 KBeugene.ilyin
#99 crypt_randombytes-2550519-99.patch68.5 KBneclimdul
#91 interdiff.txt6.94 KBklausi
#91 random-compat-2550519-91.patch71.78 KBklausi
#85 interdiff.txt1.35 KBklausi
#85 random-compat-2550519-85.patch69.48 KBklausi
#80 interdiff.txt1.35 KBklausi
#80 random-compat-2550519-80.patch69.84 KBklausi
#76 interdiff.txt606 bytesklausi
#76 random-2550519-76.patch69.48 KBklausi
#60 2550519-59.patch68.89 KBalexpott
#55 2550519-55.patch67.79 KBalexpott
#55 47-55-interdiff.txt12.56 KBalexpott
#47 2550519_47.patch64.3 KBcweagans
#46 2550519_46.patch64.3 KBcweagans
#44 2550519_44.patch63.91 KBcweagans
#44 interdiff.txt18.6 KBcweagans
#41 2550519_41.patch82.47 KBcweagans
#28 2550519-28.patch4.31 KBnullkernel
#28 interdiff-2550519-28.txt4.66 KBnullkernel
#25 interdiff-2550519-25.txt533 bytesnullkernel
#25 2550519-25.patch3.6 KBnullkernel
#18 2550519-17.patch3.08 KBpwolanin
#17 increment.txt3.78 KBpwolanin
#9 drupal-openssl_crypto_strong_checking-2550519-8.patch3.35 KBnullkernel
#7 interdiff-drupal-openssl_crypto_strong_checking-2550519-7.patch.txt1.71 KBnullkernel
#7 drupal-openssl_crypto_strong_checking-2550519-7.patch.txt3.35 KBnullkernel
#3 drupal-openssl_crypto_strong_checking-2550519-3.patch3.06 KBnullkernel
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

pwolanin created an issue. See original summary.

pwolanin’s picture

Issue summary: View changes
nullkernel’s picture

I wrote a patch which should ensure that stronger random bytes are generated.

This patch doesn't regenerate the secret key. I am unsure if doing so would cause problems with existing sites.

nullkernel’s picture

Status: Active » Needs review
joshtaylor’s picture

Is it worth checking if the system is running 5.4 if Drupal 8 minimum version is 5.5?

nullkernel’s picture

It occurred to me that we may want to check to see if random_bytes() is available. If it is, then we don't need to use openssl_random_pseudo_bytes(). It is a PHP 7 feature, though I assume is a better and more efficient source of random bytes.

http://php.net/manual/en/function.random-bytes.php

nullkernel’s picture

I have made the following changes:

  • Don't check for PHP 5.4. Anyone trying to run this code has more pressing issues than the FALSE return value.
  • Use the PHP 7 random_bytes() function if available

I am attaching another patch and an interdiff.

Disclaimer: I do not have PHP 7 installed on the computer I am using. The QA test bot is running PHP 5.5 and won't be able to test the PHP 7 code. I believe the code is correct and welcome anyone running PHP 7 to confirm that this is working as expected.

dawehner’s picture

Not sure whether we need the PHP 7 part in the issue. #2540694: Deprecate Crypt::randomBytes() in favor of PHP 7 polyfll is an issue about some form of replacement for it.

nullkernel’s picture

In my previous comment I accidentally used a .txt file extention for the patch. Reuploading for the QA bot to test.

Edit: I inadvertently got the filename wrong again (comment number) due to the comment from dawehner at the same time. dawehner, I believe my patch implements one of the proposed solutions of that issue. If the random_bytes() function is available, Crypt::randomBytes() will essentially be a wrapper for it.

dawehner’s picture

Edit: I inadvertently got the filename wrong again (comment number) due to the comment from dawehner at the same time. dawehner, I believe my patch implements one of the proposed solutions of that issue. If the random_bytes() function is available, Crypt::randomBytes() will essentially be a wrapper for it.

Well, its still not needed to fix the issue, keep things as reviewable as possible. This additional call doesn't help with that.

Someone argued that the entire fix is kinda pointless, because in case attackers care about that level of security, you have to be on the latest release (or at least the patched one from your distribution) as otherwise you have much bigger issues.

nullkernel’s picture

Overall, I like the proposed solution in the issue you linked. I looked over some of the library code but not all of it. At this time I cannot vouch for its security. It seems to do a lot to ensure good random bytes. However I think it is arguably not more efficient than the "additional call" and more difficult to review.

If it is decided to add that library as a dependency, my patch will utilise it. Whether the library is accepted as a core dependency or not, the random_bytes() function should be used if PHP, contrib modules, or website developers make the function available to Drupal.

If you or anyone else want to submit a patch (or possibly overhaul the code), I'd be interested to take a look. I think that the code I proposed is a step in the right direction.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Yeah, well its security hardening, even if we optimize for a case, which is not that likely, see previous comment.

Berdir’s picture

Doesn't matter if it is ready, but AFAIK, security hardening issues aren't critical and there's a separate tag for them?

dawehner’s picture

Priority: Critical » Major
Status: Reviewed & tested by the community » Needs review
Issue tags: +Security improvements

Good point.

Regenerate the drupal secret key

Also isn't adressed yet.

pwolanin’s picture

Lookslike a reasonable start

+
+    if (!is_null($openssl_usable)) {
+      return $openssl_usable;
+    }
+

should use isset() instead of !is_null()

pwolanin’s picture

Priority: Major » Critical

Please leave at Critical until committers have triaged. I'm unsure the real impact of this, but it is a potential security hole if the key is guessable.

pwolanin’s picture

FileSize
3.78 KB

Quick patch to fix method naming and some other things.

pwolanin’s picture

+patch

Still needs update function.

catch’s picture

+++ b/core/lib/Drupal/Component/Utility/Crypt.php
@@ -140,4 +157,39 @@ public static function randomBytesBase64($count = 32) {
+    // PHP Versions that were fixed: 5.6.12, 5.5.28, 5.4.44.

This means that we won't use openssl_random_pseudo_bytes() on distribution versions of PHP that are patched but without a version number increment. I think Debian now follows the minor PHP version numbering normally, Ubuntu LTS still patches (hence current 5.5.9 requirement), but not sure if this would affect anything else, or if it's a problem if so.

pwolanin’s picture

@catch - since there was no CVE associated with the PHP bug, it's unclear to me if Ubuntu/RHEL will backport the bugfix.

Given the recent history of openssl problems, not using it might be the better option, so perhaps we should totally remove it again, since it wasn't what we originally used here.

pwolanin’s picture

@catch - From this, it does not look like Debian is not updating the version numbers. Did you see some other announcement? https://packages.debian.org/jessie/php5

pwolanin’s picture

Given that we had guidance from Solar Designer @openwall I'm not surprised that the fallback routine looks similar to the NIST for a has-based CSPRNG:
http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf

We don't re-seed, but effectively every page request is a reseed, and I didn't dig in detail to see if there are ways we meaningfully deviate.

Also note that the polyfill for php 7 prefers /dev/urandom if available, so perhaps we should just rip out openssl?
https://github.com/paragonie/random_compat/blob/master/lib/random.php

catch’s picture

pwolanin’s picture

@catch - interesting. I see that it's implied, but unclear if it's happening?

nullkernel’s picture

I wrote an update function. I ran "drush state-get system.private_key" before and after the update, and the key had changed. I'm not sure what consequences (if any) there are to doing this. I attached a patch and interdiff.

/**
 * Regenerate system.private_key in case it was generated using a weak algorithm.
 */
function system_update_8004() {
  $state = \Drupal::state();
  $state->delete('system.private_key');
  $key = new \Drupal\Core\PrivateKey($state);
  $key->get();
}

Edit: removed irrelevant remark about backport version numbering.

Status: Needs review » Needs work

The last submitted patch, 25: 2550519-25.patch, failed testing.

nullkernel’s picture

I'm not sure why those two failures happened, or if my code even caused them. Any ideas?

As far as eliminating OpenSSL, I'm on the fence. With Windows, the code would wind up using the final fallback if random_bytes() isn't available. Ripping it out entirely may be throwing out the baby with the bathwater. I do agree that we should prefer /dev/urandom over openssl. However Windows doesn't have /dev/urandom available to provide entropy.

I think that openssl_pseudo_random_bytes() can provide Windows (and any server using the fallback) a decent source of entropy. The bytes can be thrown into the hashing function along with mt_rand(), $_SERVER, and microtime(). If a Linux distro backports the fix to an earlier version number, openssl bytes would be hashed instead of directly used.

Edit: Removed reference to getmypid().

nullkernel’s picture

I made changes based on my previous comment. I think this may get us close to resolving this issue.

In summary:

  • /dev/urandom (if available) is preferred over OpenSSL.
  • OpenSSL is used if its function is defined.
  • If the bytes from openssl_random_pseudo_bytes() aren't considered cryptographically strong, they are recycled in the fallback case's hashing function.
  • static $bytes is also now hashed as part of the fallback case. (at worst it is NULL and nothing changes)
  • checkOpenssl() has been renamed to checkOpensslStrength() and simplified.

I haven't changed the code to regenerate the private key. Perhaps it will pass the QA test cases this time around?

nullkernel’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 28: 2550519-28.patch, failed testing.

pwolanin’s picture

We should look at the php7 shiv code which sets the buffered read of /dev/urandom to 8 bytes incetead of reading a large block.

chx’s picture

> If a Linux distro backports the fix to an earlier version number, openssl bytes would be hashed instead of directly used.

that's very unlikely. As I linked above, Debian (which did such porting previously) is now more up to date with the releases instead of backporting, they are now up to 5.4.41 and I presume as security dictates they will update further. This OpenSSL bug was not marked as a security bug and if there's any other distro left that backports (which?) they most probably won't do this one -- in the past such backporting was done for security only.

Heine’s picture

Response to #28:

> /dev/urandom (if available) is preferred over OpenSSL.

This makes drupal_random_bytes insecure on Windows, when another process writes a /dev/urandom file with known content.

> If the bytes from openssl_random_pseudo_bytes() aren't considered cryptographically strong, they are recycled in the fallback case's hashing function.

Is the fallback cryptographically strong? If not, is it better, equal or worse than the result from openssl_random_pseudo_bytes? If equal or worse, do we need to change anything here?

As to #31: We should look at the php7 shiv code which sets the buffered read of /dev/urandom to 8 bytes [instead] of reading a large block.

What is the rationale for changing this and the number of bytes requested? Crypt::randomBytesBase64 defaults to 32 bytes.

cilefen’s picture

@pwolanin Can we use random_bytes() on PHP7? And if so, can we make Drupal forwards-compatible?

catch’s picture

Priority: Critical » Major

Given there was no CVE for the PHP release, I don't think this is critical. Looks like straight hardening at most.

cweagans’s picture

We should just pull in https://packagist.org/packages/paragonie/random_compat, and always call random_bytes().

pwolanin’s picture

It looks like that code has the same problem that Heine is concerned about - if a system that doesn't actually support /dev/urandom someone writes a file there you get non-random content from the file.

Is there a check we can add? Maybe limit use of /dev/urandom to cases when posix_uname()['sysname'] is Linux or Darwin?

Or more simply the posix functions are normally not available on Windows - so we can do if (function_exists('posix_getpwuid')) or the like?
http://php.net/manual/en/intro.posix.php

The only downside is a false disabling if PHP is built without posix support, but that seems uncommon?

cweagans’s picture

Opened https://github.com/paragonie/random_compat/issues/49 to discuss. The maintainers of random_compat are very responsive. I've landed a couple PRs in the last day or two.

cweagans’s picture

This may be a stupid question, but if you try to file_put_contents('/dev/urandom', 'something'); on Windows, does it actually work? Seems to me that if PHP doesn't complain loudly about that, it's a PHP bug.

cweagans’s picture

Okay, looks like random_compat had already mitigated that problem, but there's now an explicit condition for Windows hosts. See https://github.com/paragonie/random_compat/issues/49#issuecomment-147579107

cweagans’s picture

Status: Needs work » Needs review
FileSize
82.47 KB

Let's give this a try. This is a straight conversion to random_compat, now that Heine's concern is resolved upstream (the library won't even try to read from /dev/urandom if it's running on Windows).

jibran’s picture

Status: Needs review » Needs work
+++ b/vendor/paragonie/random_compat/lib/random_int.php
--- /dev/null
+++ b/vendor/paragonie/random_compat/tests/full/DieHardTest.php

We have to exclude tests folder after #2585165: Don't include vendor test code (especially mink) in the Drupal webroot.

cweagans’s picture

Status: Needs work » Needs review
FileSize
18.6 KB
63.91 KB

Ah, right. Okay, here's a new one.

Status: Needs review » Needs work

The last submitted patch, 44: 2550519_44.patch, failed testing.

cweagans’s picture

Status: Needs work » Needs review
FileSize
64.3 KB

Reroll for changes in head.

cweagans’s picture

@chx was asking about why I had the \ in front of random_bytes(). I seem to remember it being something about differences in behavior between php 5.5 and php 7.0 (given that random_bytes() is a built in in 7.0 and a user defined function in 5.5), but I'm not 100% sure, so uploading a patch without the \ to see what happens on 5.5 and 7.0.

cweagans’s picture

Apparently, I was imagining the difference :)

Reviews for #47, plz!

cweagans’s picture

Title: openssl_random_pseudo_bytes() is broken on PHP < 5.6.12, 5.5.28, 5.4.44 » Crypt::randomBytes() doesn't actually return cryptographically secure random bytes
Priority: Major » Critical
Issue summary: View changes

Changing title, bumping to critical, adding information about why that's the case to the IS.

xjm’s picture

@cweagans pinged me and asked me to take a look at this. Based on the information in the summary, my feeling is that the issue is major, but I'd defer to the security team on the priority.

chx’s picture

Issue summary: View changes
chx’s picture

Issue summary: View changes
cweagans’s picture

Issue summary: View changes
klausi’s picture

quoting Damien Tournoud: "Note: RAND_pseudo_bytes and RAND_bytes are *exactly the same thing* on *all systems except the most arcane ones*. Which is why the "fix" in PHP was not marked as a security issue, because except on some weird systems that do not have either /dev/random nor /dev/urandom, it is not, actually, a security issue."

alexpott’s picture

Here's a reroll and the tests directory properly excluded by the script.

cweagans’s picture

@klausi, the openssl docs disagree https://www.openssl.org/docs/manmaster/crypto/RAND_bytes.html (see second paragraph under "Description"), and so does the code (https://github.com/openssl/openssl/blob/master/crypto/rand/rand_lib.c#L158 vs https://github.com/openssl/openssl/blob/master/crypto/rand/rand_lib.c#L167 + https://github.com/openssl/openssl/blob/master/crypto/rand/md_rand.c#L610). I may be misinterpreting that, though, because my C is very rusty.

For the sake of argument, however, let's assume that rand_pseudo_bytes() and rand_bytes() do the same thing except for a small percentage of systems. Are we really okay with putting sites running on those systems in harms way? It'd be one thing if this were some weird unicode thing or something, but for this? These things that use random data from this class with the expectation that the output is going to be unpredictable:

* Form tokens
* CSRF tokens
* Config directories
* Password salts

If we cannot guarantee that this class will output cryptographically secure random data in all cases, we shouldn't make that claim, and if we can't make that claim, then we can't rely on it for the things that we're using it for.

Even if the openssl issue turns out to be mitigated in part or in full, there are still two other broken paths in Crypt::randomBytes() for PHP installations without ext_openssl: /dev/urandom without any checking that it's actually the right thing, and the userspace "random" data generator (which literally uses nothing suitably random).

chx’s picture

Attacking a site via creating a file called c:\dev\urandom is just devious and exceptionally elegant... even if the feasibility of it is questionable.

klausi’s picture

Status: Needs review » Needs work

Patch does not apply anymore.

I agree that the patch is a good improvement, I just don't agree on the critical priority.

catch’s picture

Priority: Critical » Major
Issue tags: +rc target

For the sake of argument, however, let's assume that rand_pseudo_bytes() and rand_bytes() do the same thing except for a small percentage of systems. Are we really okay with putting sites running on those systems in harms way?

No, which is why this is a major bug that we'll fix as soon as it's ready. However it's security hardening for a theoretical vulnerability, not an actual exploitable vulnerability - which is why it's a major bug and public, rather than critical and in the private tracker.

So downgrading back to major, but tagging RC target, could also be fixed in 8.0.1 if it doesn't get into 8.0.0.

alexpott’s picture

Status: Needs work » Needs review
FileSize
68.89 KB

Rerolled

cweagans’s picture

@klausi, Can you please elaborate on why you don't agree with it being critical?

@catch, the ext_openssl issue is not the only thing here - are you saying that all of the reasons given in the "Issue priority" section in the RC phase evaluation in the issue summary are not valid reasons for making this Critical?

@alexpott, thanks for rerolling this :)

cweagans’s picture

@catch - also, for the record, I did open a security issue about this, but since the public issue predated the security issue, I was told to work on it here. This vulnerability *is* exploitable on Windows if an attacker can also write a file to C:\dev\urandom.

klausi’s picture

Status: Needs review » Reviewed & tested by the community

@cweagans: this issue is not critical because there is no direct security vulnerability, no data loss, no unusable system.

I reviewed the patch and everything makes sense to me. The additional random.php file used for auto loading on every single page request only does a couple of file inclusions and function_exists() calls, so this should only have a tiny performance impact.

klausi’s picture

klausi’s picture

Status: Reviewed & tested by the community » Needs review

Ha, spoken too soon. Executing ab -n 500 -c 4 http://drupal-8.localhost/node/3 (with page_cache module enabled) multiple times, consistently getting the same results:

Core 8.0.x (without patch): 19ms time per request (median)
Patch: 21ms time per request (median)

That would indicate a 10% performance regression for cached pages. Can anyone reproduce those results?

EDIT: PHP version 5.6.4-4ubuntu6.4

The last submitted patch, 44: 2550519_44.patch, failed testing.

The last submitted patch, 46: 2550519_46.patch, failed testing.

The last submitted patch, 47: 2550519_47.patch, failed testing.

The last submitted patch, 55: 2550519-55.patch, failed testing.

dawehner’s picture

Is there any reason why this external library cannot use classes?

klausi’s picture

With Xdebug disabled, again ab -n 500 -c 4 http://drupal-8.localhost/node/3, page_cache enabled:

Drupal core: 16 ms time per request (median)
Patch: 17 ms time per request (median)

The random_compat library uses /dev/urandom on my system while core uses openssl_random_pseudo_bytes(), which seems to be faster. Still a difference of 6% in performance for this average page request to /node/3.

Testing autoload performance: reverting the hunks in Crypt.php so that only the new autoload code of the patch runs I get the same 16 ms time per request (median) as without the patch.

So it seems the performance regression in the patch is the usage of /dev/urandom now. If I use core and modify Crypt::randomBytes() so that openssl_random_pseudo_bytes() is not used, then I see the same 17 ms time per request (median) because it falls back to /dev/urandom.

Are we comfortable with sacrificing 1 ms on every Drupal page request just because openssl_random_pseudo_bytes() can somtimes be insecure on old PHP versions?

catch’s picture

I think we could potentially not do the Crypt::randomBytes() call in Kernel::boot(), and instead re-seed it on demand (and document you shouldn't use mt_rand() directly) to skip the 1ms regression. Need to find the issue that was added.

klausi’s picture

The mt_srand() seeding was introduced in #2140433: Port SA-CORE-2013-003 to Drupal 8.

klausi’s picture

The mt_srand() call does not make sense to me, filed #2617208: Remove mt_srand() seeding because of performance.

cweagans’s picture

@dawehner: I don't know that it's actually been discussed, but I'm not sure how much sense it makes for random_compat because it's just a polyfill for two functions.

As I said in https://www.drupal.org/node/2617208#comment-10577338, I think we can just get rid of mt_srand() here since this patch is also removing cryptographic use of mt_rand().

klausi’s picture

FileSize
69.48 KB
606 bytes

OK, here is a patch that removes the mt_srand() call from DrupalKernel.

The page cache performance tests are now equal to 8.0.x, so the removal from the Kernel bootstrapping definitely helped.

I disabled page_cache and dynamic_page_cache to measure the performance impact on an uncached page, same test with ab -n 500 -c 4 http://drupal-8.localhost/node/3:

Executed 5 times, median time per request:
core: 160,160,159,160,160 ms => median of medians: 160ms
patch: 163,165,161,162,162 ms => median of medians: 162ms

Which means we have a performance regression of about 2ms = 1% on an uncached page.

Status: Needs review » Needs work

The last submitted patch, 76: random-2550519-76.patch, failed testing.

klausi’s picture

Status: Needs work » Needs review

Random test fail, passes now.

klausi’s picture

I did some more benchmarking on a more powerful computer with the same ab -n 500 -c 4 http://drupal-8.localhost/node/3 test (page_cache and dynamic page cache disabled):

PHP 5.6.11-1ubuntu3.1, medians of 5 test runs:
core: 51,51,51,51,51 ms
patch: 51,52,51,51,51 ms

PHP 7.x master as of 2015-11-20, medians of 5 test runs:
core: 32,32,32,32,32 ms
patch: 31,32,32,32,32 ms

As we can see there is no significant performance difference on this typical Drupal request to an uncached node page.

Taking a closer look at Crypt::randomBytes() in isolation with this benchmark script which calls the static method 1 million times:

require_once 'autoload.php';

for ($i = 0; $i < 1000000; $i++) {
  \Drupal\Component\Utility\Crypt::randomBytes(55);
}

Testing with time php benchmark.php:

PHP 5.6.11-1ubuntu3.1, 5 test runs:
core: 2287,2272,2192,2291,2368 ms
patch: 4624,4600,4730,4612,4603 ms

PHP 7.x master as of 2015-11-20, 5 test runs:
core: 1590,1573,1549,1707,1545 ms
patch: 4016,3980,3971,4020,4004 ms

Quite disappointing that random_bytes() in PHP 7 performs so much worse than openssl_random_pseudo_bytes(), 2.5 times slower.

Of course this is a very artificial benchmark since the typical Drupal use case will never invoke Crypt::randomBytes() one million times. As the first ab benchmark shows a performance regression of 1% is only noticeable on older hardware (and shared hosting probably).

There is FUD flying around that openssl_random_pseudo_bytes() has a weakness https://twitter.com/ircmaxell/status/666667962959532032 , but I have not found hard evidence yet. I'm torn between the superior performance of openssl_random_pseudo_bytes() over random_bytes() and the advantage of getting rid of the code so that we don't have to maintain it anymore.

klausi’s picture

FileSize
69.84 KB
1.35 KB

Hm, this does not have to be an all or nothing issue. We can pick the random_compat library, remove the scary parts we all agree that should be removed and keep openssl_random_pseudo_bytes() for performance reasons. We can see this issue as a first step in the right direction until we know more and rather have small improvements instead of all at once.

We have here:
1) adding random_compat library, no measurable performance impact of adding it to the autoloader.
2) keeping openssl_random_pseudo_bytes() for performance reasons
3) using random_bytes() as a secure fallback, thereby removing our own custom PRNG fallback.

cweagans’s picture

As we can see there is no significant performance difference on this typical Drupal request to an uncached node page.

There's no performance hit, we get rid of code that we have to maintain, and the code we replace it with uses other/better sources of randomness before openssl. Why would we want to use openssl_random_pseudo_bytes() before that?

As you say, that's a very artificial benchmark, and is not indicative of real-world use.

IMO, we should not do anything except call random_bytes() here.

klausi’s picture

There is a performance hit for slower hardware (as demonstrated) and as soon as any contrib module gets random bytes somewhere in the critical performance path. We want to use openssl_random_pseudo_bytes() because it is faster.

So we don't agree whether using openssl_random_pseudo_bytes() is worth it or not.

In the meantime I'm offering a patch that removes the other crap where we absolutely agree that it should go. Can we do the latest patch as a compromise until we know more?

cweagans’s picture

There is a performance hit for slower hardware (as demonstrated) and as soon as any contrib module gets random bytes somewhere in the critical performance path. We want to use openssl_random_pseudo_bytes() because it is faster.

That's what I'm saying though: There isn't a real-world performance hit, and contrib modules that are getting random bytes in a critical path are doing it wrong.

The performance test that you did was a contrived example. Nobody is ever going to be getting 55 random bytes 1,000,000 times in a row. It's extremely likely that they'll be getting < 128 bytes a small handful of times, and for that, there's no observable difference, and you said that yourself:

PHP 5.6.11-1ubuntu3.1, medians of 5 test runs:
core: 51,51,51,51,51 ms
patch: 51,52,51,51,51 ms

PHP 7.x master as of 2015-11-20, medians of 5 test runs:
core: 32,32,32,32,32 ms
patch: 31,32,32,32,32 ms

As we can see there is no significant performance difference on this typical Drupal request to an uncached node page.

This method needs to return cryptographically secure pseudorandom bytes in all circumstances - no matter what OS, version of PHP, or nonstandard configuration is in use. If doing that is too slow for whatever reason, then we need to evaluate whether or not we can actually provide this functionality, and since this functionality backs a lot of our other security components, that's not a good situation to be in.

We should not sacrifice security for performance. In this case, there's only a negligible performance gain by adding back openssl_random_pseudo_bytes(), and it's just not worth it in my opinion.

If it's absolutely necessary, I'd be okay with adding openssl_random_pseudo_bytes() back in a way that a user has to specifically opt-in to using it before random_bytes() (i.e. it should be off by default). However, I think we should use random_bytes() and then open a followup to determine if openssl_random_pseudo_bytes() is really worth the performance gain in all scenarios (and there are a *lot* of them - Windows wasn't considered here at all, nor were systems that are using ext_libsodium). I suspect that it's not, but I'd want to verify that in a test that reflects real-world use (and not just the raw speed of execution of each method 1,000,000 times).

klausi’s picture

We don't know the use cases of contrib modules and using random bytes in the critical performance path might be something they want to do - so this is not necessarily wrong.

I'm still missing evidence here that openssl_random_pseudo_bytes() is insecure (even in the older PHP versions). I agree with Damien Tournoud that we just have FUD about it right now. If we cannot come up with an attack scenario that describes on what system and what configuration openssl_random_pseudo_bytes() is exploitable we should stick with it.

klausi’s picture

Talked to alexpott about this and he is in favor of getting as much custom crypto code removed here as we can, off-loading this to random_compat and PHP 7 core. The performance loss for that is small enough.

We have eliminated the worst performance offender in DrupalKernel in this patch, so I guess I can live with random_bytes() only for the sake of making progress here.

Patch attached that uses random_bytes() only and is the same as in #76.

cweagans’s picture

@klausi, Sorry I missed you in IRC over the weekend. There are details about the problem here: https://github.com/paragonie/random_compat/issues/6#issuecomment-119564973

+1 to #85

klausi’s picture

Status: Needs review » Reviewed & tested by the community

OK, me + cweagans + alexpott agree, no objections, enough for RTBC.

neclimdul’s picture

I suspected that the extra requires random_compat does was causing the slowdown on the "slow" test. Klausi confirmed there was no opcache on that instance which is good enough for me to write off my concerns.

+1 RTBC.

David_Rothstein’s picture

+            "name": "paragonie/random_compat",
+            "version": "1.1.0",

There's a newer version of the library available (1.1.1) - should the patch use that instead?

klausi’s picture

Assigned: Unassigned » klausi
Status: Reviewed & tested by the community » Needs work

yes, will do a reroll.

klausi’s picture

Assigned: klausi » Unassigned
Status: Needs work » Needs review
FileSize
71.78 KB
6.94 KB

Rerolled with composer update paragonie/random_compat.

neclimdul’s picture

Status: Needs review » Reviewed & tested by the community
cweagans’s picture

+1 to RTBC on this. Thanks for moving this forward Klausi. I've been stupidly busy lately. I went ahead and opened the followup I mentioned earlier: https://www.drupal.org/node/2629926

While we're waiting on this to land in 8.0.x and/or 8.1.x, we should figure out how we might backport this to D7 and/or D6 (per the tags on this issue). Pulling in a Composer lib in D8 is reasonably straightforward, but I don't know that we have a reasonable way to do that in D7 or D6. Any ideas here?

cweagans’s picture

klausi, looks like someone else saw the performance issue you were seeing (I could not reproduce). https://github.com/paragonie/random_compat/issues/78 The fix for that is included in 1.1.1. Does that change resolve the issue satisfactorily?

alexpott’s picture

@cweagans so we should update the patch - no?

cweagans’s picture

@alexpott, klausi already rerolled the patch with 1.1.1 in #91. Just wanted to check to see if the new release resolved the issue he was seeing. Still RTBC from my perspective.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 91: random-compat-2550519-91.patch, failed testing.

The last submitted patch, 91: random-compat-2550519-91.patch, failed testing.

neclimdul’s picture

Status: Needs work » Needs review
FileSize
68.5 KB

phantomjs collision. Conflicts in composer.lock and installed.json.

Rebased, reset files to HEAD version and rebuilt with composer.phar update --lock

cweagans’s picture

Status: Needs review » Reviewed & tested by the community

LGTM. Back to RTBC.

alexpott’s picture

Version: 8.0.x-dev » 7.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

Committed 5106176 and pushed to 8.0.x and 8.1.x. Thanks!

  • alexpott committed 1aa1fbe on 8.1.x
    Issue #2550519 by klausi, nullkernel, cweagans, alexpott, pwolanin,...

  • alexpott committed 5106176 on
    Issue #2550519 by klausi, nullkernel, cweagans, alexpott, pwolanin,...
alexpott’s picture

I committed this to 8.0.x because afacis this patch does not add any new functionality to Drupal 8 and therefore is only a bug fix and patch eligible. I think in most cases adding a new library would be a minor version activity.

eugene.ilyin’s picture

Commits in #102 & #103 doesn't looks correct.
Function random_bytes() available only in PHP 7.
But according to this page https://www.drupal.org/requirements PHP 5.5.9 is enough for Drupal 8.

I use PHP 5.5.9 and I have Fatal error :(

Fatal error: Call to undefined function Drupal\Component\Utility\random_bytes() in /../test8/core/lib/Drupal/Component/Utility/Crypt.php on line 36

eugene.ilyin’s picture

Status: Patch (to be ported) » Needs work
eugene.ilyin’s picture

alexpott’s picture

@eugene.ilyin any chance you can provide a full backtrace? The patch adds a polyfil library for older versions of PHP and the patches are tested on PHP5.5

eugene.ilyin’s picture

FileSize
3.67 KB

@alexpott, I've added xdebug backtrace in the attached archive. Is it enough?
Also for information, I've installed composer manager and I've updated my libraries via "composer drupal-update"
Maybe something went wrong...

alexpott’s picture

@eugene.ilyin yeah you seem to be missing vendor/paragonie/random_compat - if you run composer install --dry-run in the drupal root what happens? And please ensure that composer is up-to-date.

eugene.ilyin’s picture

@alexpott and @bojanz helped me in IRC. Trouble was in my personal state of directory "vendor".

How I've fixed it:
1. I've executed "composer install --dry-run" in root directory
2. After that I've tried to execute "composer drupal-update" but I had an error
3. I've turned back the directory "vendor" to clean state (as in drupal core)
4. I've executed "composer drupal-update" again and my trouble has gone

eugene.ilyin’s picture

Status: Needs work » Patch (to be ported)

  • alexpott committed 6c443ac on 8.0.x
    Revert "Issue #2550519 by klausi, nullkernel, cweagans, alexpott,...
alexpott’s picture

After discussing with @catch we've decided to revert this patch because adding dependencies is proving to be tricky. For example if you following the instructions to update only the /core directory and then run composer install in the drupal root directory nothing happens - it does not get the new paragonie/random_compat. Therefore, it seems sensible to give ourselves some more time to work out how to give the best advice on updating core dependencies.

catch’s picture

Just to clarify this is reverted against 8.0.x, the 8.1.x patch is still in as is.

cweagans’s picture

@alexpott @catch So should this work be cleaned up for 8.0.x? Or should we just have users wait to update to 8.1.0 to get this improvement? I'm not really sure why this was rolled back though. If you're using Composer + a subtree split of /core, you'll have to wait for the subtree repo to be updated + wait for Composer to generate new package metadata that includes random_compat as a dependency (which would happen when 8.0.2 is tagged). I'm fairly certain that running on head + composer_manager won't pick up new dependencies until that happens.

alexpott’s picture

@cweagans here is why... https://github.com/wikimedia/composer-merge-plugin/issues/96 - basically we're breaking the promise of update ./core and you're good to go. And there is no composer command to help either - apart from composer update and that is going to do way more than just install the dependency.

catch’s picture

@cweagans for me I'd just have this wait until 8.1.x - especially given the security improvement is as far as we know theoretical.

#116 is interesting though, if tagging the release made it all work again, but not sure it's worth waiting to find out. We'd probably need an 8.1.0-beta1 to test that successuflly without impacting real sites.

cweagans’s picture

Eh. Let's just wait for now. People that really want the change can apply the patch to 8.0.x, and they'll likely know how to deal with the composer issues too.

So how should we go about getting this into D7? Just download random_compat, shove it into includes, and require() it from bootstrap.inc? The other changes are pretty straightforward - just want to know what's going to be acceptable in terms of getting the lib loaded.

catch’s picture

Up to David in the end, but #119 is about what I'd expect.

David_Rothstein’s picture

So how should we go about getting this into D7? Just download random_compat, shove it into includes, and require() it from bootstrap.inc?

I guess, though it does seem a bit wasteful to load all that code when it might not be used.

Another way (which I think may have made sense for D8 too) is to load it from within drupal_random_bytes() the first time that function is called?

A couple other questions I had just now skimming through the D8 patch (I guess they apply to both D7 and D8):

  1. +        if (!function_exists('random_bytes')) {
    +            /**
    +             * We don't have any more options, so let's throw an exception right now
    +             * and hope the developer won't let it fail silently.
    +             */
    +            function random_bytes()
    +            {
    +                throw new Exception(
    +                    'There is no suitable CSPRNG installed on your system'
    +                );
    +            }
    +        }
    

    Do we know that this condition won't ever be reached in Drupal? Because we aren't doing anything to handle it...

    Currently in drupal_random_bytes() we always return something and never throw an exception.

  2. This is a new library so should we be listing it in Drupal's COPYRIGHT.txt file?
Crell’s picture

The argument made on PHP Internals was that if you ask for cryptographically secure random bytes, and those cannot be provided, then the only secure thing to do is bail out. Proceeding with an insecure string (or worse, false aka empty string) is a security hole. That is, if you hit that exception your site's environment is not safe to use in the first place, and a fatal uncaught exception is appropriate. (Or, alternatively, we could see about translating it to a nicer-looking fatal error message.)

For Drupal 7, we could wrap the include statement in a version_compare() check as it's only needed < PHP 7.0.0. But really, that's what the if (function_exists()) stuff is doing already so I wouldn't worry about it too much in D7 or D8.

Edited: Corrected the direction of the carrot

cweagans’s picture

I guess, though it does seem a bit wasteful to load all that code when it might not be used.

Another way (which I think may have made sense for D8 too) is to load it from within drupal_random_bytes() the first time that function is called?

It's not a lot of code all things considered - likely not much more than what drupal_random_bytes() already contains.

I *think* this code is going to get loaded on most page requests anyway, but I have no objection to loading it on the first drupal_random_bytes() call.

Do we know that this condition won't ever be reached in Drupal? Because we aren't doing anything to handle it...

It's very rare that a system would be so thin on sources of randomness. IIRC, when I did some digging on phpinfo for a bunch of shared hosts a couple years ago for something else, openssl was enabled 100% of the time, even on the most obscure of hosts, so that would be the most likely worst case scenario.

neclimdul’s picture

I think this is a missunderstanding. @eugene.ilyin could you provide the steps you did to upgrade? My guess from Alex's upstream report is that composer.lock was maybe not updated? http://cgit.drupalcode.org/drupal/tree/core/UPGRADE.txt#n67 requires all files be updated. If you don't follow that process things will probably go wrong.

Additionally from the discussion people might be looking for composer.phar update --lock which will pull in things from composer.json that are missing from composer.lock. This is a pretty important command for people using composer_manager or similar contrib solutions for managing /vendor.

David_Rothstein’s picture

It's not a lot of code all things considered - likely not much more than what drupal_random_bytes() already contains.

I *think* this code is going to get loaded on most page requests anyway

Yeah, I think that part is probably fine as is.

It's very rare that a system would be so thin on sources of randomness. IIRC, when I did some digging on phpinfo for a bunch of shared hosts a couple years ago for something else, openssl was enabled 100% of the time, even on the most obscure of hosts, so that would be the most likely worst case scenario.

I guess what I want to avoid is throwing a runtime exception on live sites... The library does appear to throw exceptions in a couple different places under a couple different scenarios.

So if we're adding some new requirements to Drupal here that didn't exist before, it should at least be in hook_requirements() somewhere. And we should think a bit how it should be handled on upgrade.

One possible solution would be to keep our old code around, but only use it as a fallback in the case where the library throws an exception. And when a site is in that situation, throw a REQUIREMENT_ERROR in hook_requirements().

This is based on the idea that our current drupal_random_bytes() is not a complete security disaster, even if it's not perfect either (i.e. that a site can still operate with a reasonable level of security in that case, even though the security could be improved).

David_Rothstein’s picture

I had intended to leave a related issue with the above comment, but it got eaten.

cweagans’s picture

One possible solution would be to keep our old code around, but only use it as a fallback in the case where the library throws an exception.

I strongly object to this. The polyfill only throws an exception when random bytes cannot be generated for whatever reason (usually, it's a lack of a trustworthy source of randomness). If we're saying that drupal_random_bytes() is going to return cryptographically secure random bytes, then it should either do that or throw an exception when it can't. An exception is (in this case) the least undesirable thing, and the most undesirable thing is that we return more predictable random bytes silently.

All of that said, it's very rare that an environment won't have at least one of the polyfill methods available. I think every shared hosting company that I've looked at has at least two options (usually mcrypt and openssl, but some windows hosts have the COM extension enabled too). I don't think this is something to worry about.

A hook_requirements() entry, however, might be a good idea. Just ensure that the host that Drupal is running on has at least ONE of the handfull of polyfill methods available.

stefan.r’s picture

Just bumping this as this would be good to get into 7.x

  • alexpott committed 1aa1fbe on 8.3.x
    Issue #2550519 by klausi, nullkernel, cweagans, alexpott, pwolanin,...

  • alexpott committed 1aa1fbe on 8.3.x
    Issue #2550519 by klausi, nullkernel, cweagans, alexpott, pwolanin,...
klausi’s picture

Status: Patch (to be ported) » Needs review
FileSize
74.16 KB

Here is a first D7 patch. I did not include a hook_requirements() check because drupal_random_bytes() is used so early. I agree with @cweagans that a site should fail fast if there is no source of secure random bytes.

stefan.r’s picture

klausi’s picture

Removed some build scripts in random_compat that we really should not ship with Drupal.

The last submitted patch, 131: random_bytes-2550519-D7.patch, failed testing.

daffie’s picture

Why are we going to use version 2 of paragonie's random_compat library for D7 and version 1 for D8. Should we upgrade the library for D8?

klausi’s picture

We should use version 2 in both, the issue to upgrade it in D8 is #2763787: Upgrade random_compat to latest version.

pwolanin’s picture

Priority: Major » Critical

There is some suggestion this is affecting production sites, so we should consider this critical

pwolanin’s picture

Should we try/catch in drupal_random_bytes and retain the existing fallback generator? Given that it came from Solar Designer, it's better than an uncaught exception or upgrade failure.

David_Rothstein’s picture

Priority: Critical » Major

Right, like I said above, I really don't like the idea of whitescreening someone's production site just because our random number generator doesn't reach a level of security that we never claimed it had in the first place...

I think allowing the fallback to be used, but putting something about it in hook_requirements() (perhaps a REQUIREMENT_ERROR on runtime and a REQUIREMENT_WARNING on install?) would really be good enough.

David_Rothstein’s picture

Priority: Major » Critical

Oops, cross-post (had the issue open in a browser tab for a while). Not entirely sure what "affecting production sites" means in this context though...?

Fabianx’s picture

Status: Needs review » Needs work

- CNW for the try {} catch

Just got a report that on some site this generated the same Session ID twice in a day ...

sarciszewski’s picture

Right, like I said above, I really don't like the idea of whitescreening someone's production site just because our random number generator doesn't reach a level of security that we never claimed it had in the first place...

By naming it Crypt::randomBytes, you're implicitly claiming it will be cryptographically secure. Using random_compat v1 will guarantee that you have a CSPRNG in most installations, but its fallback to OpenSSL will cause problems like https://github.com/ramsey/uuid/issues/80.

Using v2 guarantees a level of security comparable to what PHP 7 offers (and is, therefore, a true polyfill).

My recommendation is to set the version requirement to ^1|^2 so people can choose whichever one works best for their plugins/extensions.

(In case it wasn't clear: I work for Paragon Initiative Enterprises, whose namespace random_compat is published under.)

David_Rothstein’s picture

Title: Crypt::randomBytes() doesn't actually return cryptographically secure random bytes » Crypt::randomBytes()/drupal_random_bytes() doesn't actually return cryptographically secure random bytes

By naming it Crypt::randomBytes, you're implicitly claiming it will be cryptographically secure.

The name of the function in Drupal 7 is drupal_random_bytes(), not Crypt::randomBytes(), and the documentation at https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/drup... only claims that it is "highly randomized" and "uses the best available pseudo-random source", nothing more than that.

But yes, it is confusing that the issue title here dates from the Drupal 8 version of the patch :)

Just got a report that on some site this generated the same Session ID twice in a day

fallback to OpenSSL will cause problems like https://github.com/ramsey/uuid/issues/80.

So we'd probably be better off never using OpenSSL at all, to be safe?

In any case, there is definitely no need to keep the OpenSSL code from the current version of drupal_random_bytes() - I assume the only thing we'd need to keep from that is the final fallback, i.e. the part from here onward:

  // If we couldn't get enough entropy, this simple hash-based PRNG will
  // generate a good set of pseudo-random bytes on any system.

Since our goal is to hand this off to random_bytes() whenever possible, but just keep a single guaranteed backup method in place to prevent whitescreening the site if all else fails.

klausi’s picture

Status: Needs work » Needs review
FileSize
71.33 KB

Ah, we forgot to remove the performance critical mt_srand() seeding during Drupal boostrap as we did for Drupal 8.

Still no exception handling or hook_requirements(), feel free to pick that up.

klausi’s picture

FileSize
641 bytes

Forgot interdiff.

pwolanin’s picture

+++ b/includes/common.inc
@@ -5257,10 +5257,6 @@ function _drupal_bootstrap_full() {
-  // Ensure mt_rand is reseeded, to prevent random values from one page load
-  // being exploited to predict random values in subsequent page loads.
-  $seed = unpack("L", drupal_random_bytes(4));
-  mt_srand($seed[1]);

Why do we want to remove this - just to avoid forcing a call to drupal_random_bytes()?

klausi’s picture

Yes, getting random bytes is slow on php5, see analysis in #71.

pwolanin’s picture

Here's with fallback for Exception

klausi’s picture

  1. +++ b/includes/bootstrap.inc
    @@ -2242,7 +2242,42 @@ function drupal_base64_encode($string) {
    +  catch (\Exception $e) {
    

    This will fail on PHP 5.2 because it does not understand the "\" namespace operator.

  2. +++ b/includes/bootstrap.inc
    @@ -2242,7 +2242,42 @@ function drupal_base64_encode($string) {
    +      // Initialize on the first call. The contents of $_SERVER includes a mix of
    +      // user-specific and system information that varies a little with each page.
    

    line length now over 80 characters.

Status: Needs review » Needs work

The last submitted patch, 148: 2550519-147.patch, failed testing.

pwolanin’s picture

Status: Needs work » Needs review
FileSize
71.46 KB
1.16 KB

oh, I'm an idiot - I passed the --stat flag to git when making the prior patch.

This should address klausi's concerns.

klausi’s picture

Looks good! Now we also need that try/catch block in system_requirements(). It should show an error on runtime and install time when random_bytes() throws an exception, right?

BTW I'm still in favor of not catching the exception, but I guess any progress is better than nothing?

We are running patch #144 on our production sites successfully now.

sarciszewski’s picture

This will fail on PHP 5.2 because it does not understand the "\" namespace operator.

https://github.com/joomla/joomla-cms/blob/91749f845dcf20e097801ecd75da8e...

klausi’s picture

@sarciszewski: we are talking about the old stable version of Drupal here, which is Drupal 7 and was released in 2011. It's minimum requirement is PHP 5.2 and we are not breaking that compatibility just for fun.

Drupal 8 of course requires PHP 5.5 :)

pwolanin’s picture

Status: Needs review » Needs work

Ok, so needs work still for hook requirements?

pwolanin’s picture

Status: Needs work » Needs review
FileSize
72.81 KB
1.34 KB

Added a check to system_requirements()

Status: Needs review » Needs work

The last submitted patch, 156: 2550519-156.patch, failed testing.

The last submitted patch, 156: 2550519-156.patch, failed testing.

joseph.olstad’s picture

@pwolanin

The reason why your patch #156 failed:
+ require_once DRUPAL_ROOT . '/includes/random_compat/lib/random.php';

random_compat and this library was not included in the patch, therefore travis ci will fail during testing.. Try making a new patch that adds this random_compat/lib/random.php

To do this you will git add this and commit it in your local with the other changes , then to generate the patch you will do a git diff of the resulting commit hash tag vs the previous hashtag.

19:43:52 error: patch failed: scripts/run-tests.sh:142
19:43:52 error: scripts/run-tests.sh: patch does not apply

This is pretty easy to generate a new patch, what version of this random.php library are you wanting to include?

klausi’s picture

@joseph.olstad: the latest patch already contains the library in version 2.0.2, so there is no need to roll a new patch.

Queuing the patch for testing again.

pwolanin’s picture

Status: Needs work » Needs review

I'm not sure what the test fails were - seem to not be there on re-test

joseph.olstad’s picture

Testbot says its good after a kick. So yes the patch was fine after all. Blame it on the testbot.

David_Rothstein’s picture

+    'title' => $t('Random byte generation'),
...
+    $requirements['php_random_bytes']['description'] = $t('Drupal could not generate secure random bytes and is using a slow, less-secure fallback generator. %exception_message', array('%exception_message' => $e->getMessage()));
+    $requirements['php_random_bytes']['value'] = $t('Failed to fallback random generator');
+    $requirements['php_random_bytes']['severity'] = REQUIREMENT_ERROR;

This could use some kind of help text (or, probably better, a link) explaining how to fix the problem.

Wording could be a bit less technical also, maybe like:
Title: "Random number generation"
Summary: "Less secure"
Description: "The server is unable to generate highly randomized numbers, which means certain security features like password reset URLs are not as secure as they should be. This site is using a slow, less-secure fallback generator instead..." ... something like that.

Do we really want this to be a REQUIREMENT_ERROR even on install/update? Won't that prevent Drupal from being installed at all?

klausi’s picture

Status: Needs review » Needs work

I think REQUIREMENT_ERROR is fine. You should really fix your server setup first when there is no source of secure random numbers at all. We only want to permit usage of our weak random number generator for existing sites so that they don't white screen on an update.

Which means we should not run this check in the "update" phase to always allow sites being updated.

YesCT’s picture

I'll work on the wording changes asked for in #163

YesCT’s picture

Status: Needs work » Needs review
FileSize
1.48 KB
72.91 KB

wasn't sure if the "better" to use a link meant making a handbook page, or linking to an existing page. maybe this is text change for now, and the link can be a separate issue?

there is also some whitespace at the end of lines in the patch that we can clean up. not sure if we want that in the 7.x patch, or if it should keep it to match 8.x.

I looked to see if these text changes would mean a change also in a test, and didn't see any.

pwolanin’s picture

Should skip the check in update phase per klausi?

pwolanin’s picture

Here's a change to skip the check on update

YesCT’s picture

1.

I asked @pwolanin about only giving the message if not during an update. The requirement will still show up on the status page, it just wont completely die when trying to run update. Seems ok to me.

--

2.

I also asked about no test fail with the change of the exception wording. Seems we could write a test that redefines random_bytes() to force an exception.

See, in bootstrap.inc

function drupal_random_bytes($count)  {
  require_once DRUPAL_ROOT . '/includes/random_compat/lib/random.php';
  try {
    return random_bytes($count);
  }
  catch (Exception $e) {

But, since random_bytes is in php 7, it might cause a fatal in 7, or we would need a condition in the test to only do the test logic if php is less than 7.

if (PHP_VERSION_ID < 70000) {

So adding a test might not be worth it.

Not sure if the CI problem with d7 php7 tests is still an issue. related: #2656548: Fully support PHP 7.0 in Drupal 7

YesCT’s picture

Status: Needs review » Needs work

I read the entire (non library part) of the patch.

1.

the white space warning when I did the git apply is from the library we are including, so nothing to change there. see my #166.

2.

+++ b/includes/bootstrap.inc
@@ -2271,8 +2257,8 @@ function drupal_random_bytes($count)  {
-      // Initialize on the first call. The contents of $_SERVER includes a mix of
-      // user-specific and system information that varies a little with each page.
+      // Initialize on the first call. The $_SERVER variable includes user and
+      // system-specific information that varies a little with each page.

There looks like there might be some unrelated nit fixing. (changing comment that is over 80 chars, but not changing the meaning). This hunk should be reverted. https://www.drupal.org/core/scope#creep

I think this happened because of #149 which was looking at an interdiff, which made it looked like those lines were part of a larger change, but when looking at the patch, it is more clear that that section is not being changed.

3.

ha

+++ b/includes/bootstrap.inc
@@ -2230,39 +2230,25 @@ function drupal_base64_encode($string) {
+ * In PHP 7 and up, this uses the built-in PHP function random_bytes().
+ * In older PHP versions, this uses the random_bytes() function provided by
+ * the random_compat library.

nit. wrap to 80.

===

I think the only core gate blocking thing might be 2. the unrelated change. @pwolanin is gonna address both though really soon. so not fixing the nit myself.

pwolanin’s picture

Status: Needs work » Needs review
FileSize
72.3 KB
1.41 KB

We can only write a test for PHP < 7.0 so it's not clear to me that's really useful and we don't have exiting test coverage of the byte quality or any such.

I reverted the text change since it's not strictly related to the fix as YesCT suggested.

The changes in #166 look like they match what David suggested.

YesCT’s picture

Status: Needs review » Reviewed & tested by the community

thanks! that looks great. expecting the bot to return green. setting to rtbc.

YesCT’s picture

Issue summary: View changes
pwolanin’s picture

David_Rothstein’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: -Drupal bugfix target +Drupal 7.60 target
  1. +      $requirements['php_random_bytes']['description'] = $t('The server is unable to generate highly randomized numbers, which means certain security features like password reset URLs are not as secure as they should be. Instead, this site is using a slow, less-secure fallback generator. %exception_message', array('%exception_message' => $e->getMessage()));
    

    This still needs help text or a link which explains how to fix the problem, especially if we're going to go ahead and make this a hard requirement for new installs. Right now the exception message (from the library) just says "There is no suitable CSPRNG installed on your system", which is not useful. We need to give people some information about what they need to install on their server to be able to use this. It could be a link to some outside documentation (if that exists) or a page on drupal.org, etc.

  2. I wonder if anyone has tested this on a typical Windows setup. The reason I ask is that I just confirmed that on my standard Linux setup, /dev/urandom is the only place that this library can actually get random numbers from... if it didn't do that, it would have failed on my machine. And /dev/urandom is not available on Windows.
  3. -  // Ensure mt_rand is reseeded, to prevent random values from one page load
    -  // being exploited to predict random values in subsequent page loads.
    -  $seed = unpack("L", drupal_random_bytes(4));
    -  mt_srand($seed[1]);
    

    Isn't this still needed for the fallback behavior of drupal_random_bytes()? (Perhaps it doesn't need to be in the bootstrap anymore, but seems like it's still necessary somewhere in the case where the fallback is being used.)

  4. +      $requirements['php_random_bytes']['value'] = $t('Successful');
    +    } catch (Exception $e) {
    

    Maybe it should be "Secure" rather than "Successful"?

    Also (minor), catch (Exception $e) { should be on its own line.

David_Rothstein’s picture

Also the help text doesn't look quite right for the case where an installation is actually being blocked... it says "this site is using a slow, less-secure fallback generator" but in fact the site isn't using anything because Drupal won't let you install it.

Heine’s picture

I've removed the exception from the status message and added a link to https://www.drupal.org/docs/7/system-requirements/php#csprng which we can add.

In addition, I discovered that many of our sites will default to the fallback, because they use openbase_dir without /dev/urandom and do not have any other random source such as mcrypt or libsodium. This change probably needs a big note in the release notes.

Heine’s picture

Status: Needs work » Needs review
pwolanin’s picture

Status: Needs review » Needs work

Created a stub at https://www.drupal.org/docs/7/system-requirements/php#csprng

Seems like we need some notes there and need to mention how to enable /dev/urandom under open basedir?

Possibly in the status message we need to check if open_basedir is set and omits that path as suggest that fix?

pwolanin’s picture

We can apply a check for the open_basedir setting like the library does in terms of setting an error message

https://github.com/paragonie/random_compat/blob/master/lib/random.php#L8...

Heine’s picture

Status: Needs work » Needs review
FileSize
73.61 KB
2.54 KB

I've added the suggestions in #179, #180. Not certain about the wording.

YesCT’s picture

I'm looking into adding the mt_rand seed back. Doing some research.

Heine’s picture

I did not run a full install on Windows, just random_compat on Windows 10 with php-5.6.26-nts-Win32-VC11-x86.zip . random_compat was able to get random bytes.

The 5.6 binaries from windows.php.net all have mcrypt statically linked; this is also what random_compat uses. When I disabled the mcrypt option in random_compat, it threw an exception.

sarciszewski’s picture

Please don't fall back to mt_rand(), ever.

http://www.openwall.com/php_mt_seed/

klausi’s picture

@sarciszewski: we don't fall back to mt_rand() alone - in fact it plays a very small role in the PRNG algorithm we have. So the tool you linked above is worthless when you try to attack a Drupal site using the fallback algorithm.

That being said I agree that we should not have this fallback code since Drupal should not be in business of inventing a PRNG. What could possibly got wrong? :-P
It is a sad sacrifice to not break existing sites on old systems.

cweagans’s picture

> It is a sad sacrifice to not break existing sites on old systems.

These sites are already broken and the worst part is, they probably don't know it.

heddn’s picture

What if we watchdog log or throw warnings as loudly as we can? Drupal set message warning would be another option. Similar to what we did for deleted but not uninstalled modules in 7.50

cweagans’s picture

IMO, we should just wrap all of the mt_rand() stuff in an off-by-default flag in settings.php and use the correct PRNG behaviors from random_compat. If a site is really incapable of using any of those methods, then they can opt in to the insecure mt_rand() implementation. If they opt in, we should continue to complain loudly at them and encourage them to move their site to a more capable host.

Heine’s picture

As to #186, whether sites are currently broken depends their PHP version and on the forking model of the server.

PHP bug 70014 is fairly uninteresting (and fixed in supported PHP releases) compared to PHP bug 71915.

How about doing a staged release?

- 7.5x - remove use of openssl_random_pseudobytes, prevent installation for new sites, complain on status / admin via requirements on existing sites.
- 7.6x - remove the fallback.

This would require a change to the message in #181 to tell the user that the fallback will be removed in 7.6x.

Both releases would need very explicit release notes explaining the changes and steps on how to solve the warning / error.

stefan.r’s picture

Assigned: Unassigned » David_Rothstein

Assigning to David_Rothstein for thoughts on #189.

pwolanin’s picture

Assigned: David_Rothstein » Unassigned

@sarciszewski - the fallback generator in the patch was originally contributed to Drupal by Solr Designer of openwall - so while it's less than ideal, its worst-case behavior isn't as terrible as some other options. Note also that we are including this fallback only to avoid breaking existing sites on upgrade.

pwolanin’s picture

Assigned: Unassigned » David_Rothstein

adding back assigned to David.

I fell like we are over-thinking this - the fallback code has been in D7 a long time and by blocking new installs I think we are taking a reasonable step to push people to sort this out without causing a lot of pain.

I would also like to see someone describe an attack vector that makes it worth a lot of debate. About the only way I see to directly access some output of the random stream is by causing a new session ID to be generated. From that I don't think you can gain any real insight into the session ID the next user would get.

We can also encourage people to upgrade to php 7 which should never be broken. I suppose we could re-throw the exception on php 7+.

pwolanin’s picture

YesCT’s picture

Status: Needs review » Reviewed & tested by the community

Read the whole patch.

Noticed the message changed from "The server" to "Drupal". (In addition to the other changes in this patch, this one is nice. Cause it makes the message more accurate, and ... while we try not to use the word Drupal in drupal messages, here it is appropriate, and also common in other requirement messages.)

pwolanin’s picture

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 195: 2550519-195.patch, failed testing.

pwolanin’s picture

Status: Needs work » Reviewed & tested by the community

Odd - seems to have been a sporadic failure in upgrade tests. Passed on re-test.

Fabianx’s picture

This is a David issue and a 7.60 target, so only a quick fly-by review:

--

+++ b/includes/bootstrap.inc
@@ -2280,6 +2266,8 @@ function drupal_random_bytes($count)  {
+        mt_srand();

I might be mistaken, but would it not be easier to just take a hash_base_64 (which uses the sites' private key) of the microtime(NULL) as input to mt_srand().

Would that not ensure a secure and non-guessable mt_srand() start?

  • alexpott committed 1aa1fbe on 8.4.x
    Issue #2550519 by klausi, nullkernel, cweagans, alexpott, pwolanin,...

  • alexpott committed 1aa1fbe on 8.4.x
    Issue #2550519 by klausi, nullkernel, cweagans, alexpott, pwolanin,...
David_Rothstein’s picture

Assigned: David_Rothstein » Unassigned

The patch itself might be ready, but the Status Report messages are linking to https://www.drupal.org/docs/7/system-requirements/php#csprng which doesn't have any actual documentation at the moment (it literally just says "See discussion on Drupal 7 issue"). Especially if the patch is going to block new installs, we need actual documentation that explains to people how they can fix the problem on their site.

And I have to say I'm still uneasy about blocking new installs - reading https://www.drupal.org/node/2763787#comment-11650589 and subsequent comments (plus Heine's comments above in the current issue) it seems like there are definitely real-world cases where this happens.

If we're talking about splitting this up into stages (like in #189) I would think the simplest way to do that would be (1) add the library and use it whenever possible, thereby getting more secure random bytes, (2) anything and everything related to adding warnings/errors in the user interface. Because (1) is certainly a completely uncontroversial and reasonably simple change, and it should give the actual security benefit to most sites.

joseph.olstad’s picture

Issue tags: -Drupal 7.60 target +Drupal 7.61 target

Bumping to 7.61. This didn't make it into 7.60.

joseph.olstad’s picture

Ayesh’s picture

Can we please make it that we don't make any performance disadvantage for PHP 7+ users? With the current patch, we include the random.php file for every request, even if the user is running PHP 7. The attached patch short-wires the function_exists() calls the random.php file does anyway, but before including the file.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 204: 2550519-204.patch, failed testing. View results

joseph.olstad’s picture

MustangGB’s picture

Issue tags: -Drupal 7.64 target +Drupal 7.69 target
MustangGB’s picture

Issue tags: -Drupal 7.69 target +Drupal 7.70 target
apaderno’s picture

Issue tags: -Needs backport to D6

I take that it's too late to back-port it to Drupal 6, since it's not supported anymore.

joseph.olstad’s picture

izmeez’s picture

@kiamlaluno Any interest in backport for Drupal 6 can be raised in the D6LTS issues, https://www.drupal.org/project/issues/d6lts

apaderno’s picture

@izmeez Yes, but in this queque, Needs backport to D6 for a Drupal 7 issue is useless, as core maintainers don't do back-ports to Drupal 6 anymore.

izmeez’s picture

yes, understood.

izmeez’s picture

Did this get committed to Drupal 7.83 or is it still pending?

mcdruid’s picture

Issue tags: -Drupal 7.74 target

Nope, this wasn't in 7.83

We'll need to add it to the meta priorities issue for the next release (which I have not spun up yet, but will do soon unless somebody beats me to it).

izmeez’s picture

Thanks, it will need a re-roll or more to make it work with Drupal 7.83 version.

mfb’s picture

And the random_compat library is now on version 2.0.20 so presumably should be updated? (At this point, years later, the use of random_bytes() seems like the more useful part of this patch :)

Nikolay Shapovalov’s picture

sinduri’s picture

Reroll for #144 for Version 7.93

sinduri’s picture

Reroll for #144 for Version 7.93
(Missed Copyright in #219)

apaderno’s picture

Status: Needs work » Needs review
poker10’s picture

Status: Needs review » Needs work

Last three patches seems to be wrong and causing a noise in this issue. @zniki.ru and @sindurig - when doing rerolls, please check carefully which patches are you rerolling and which are the most recent.

I see patches #181, #195 and #204 as more refined comparing to the patch #144 (which was rerolled in #218). I do not see a reason to reroll such an old patch, especially if the reasons are not mentioned in the respective comment. Thanks!

Also the latest patch (#220) fails PHPLint, therefore switching this back to Needs Work.

--------------

Bottom line: PHP 5.6 was EOL four years ago. I think we would need to reconsider if it is still worth adding a new library to actively support the ancient PHP versions in this D7 phase (as there should not be any problems with this starting from PHP 7), or if we should close this.

klausi’s picture

Sorry for the confusion poker10, Sinduri was just rerolling this patch that we have been using for years when we were not on PHP 7 yet.

The reason that this critical bug is still open is that this is or was a security issue. The PHP ticket mentioned in the issue summary now has a CVE, but I have no idea if Linux distros patched this now in old PHP versions or not.

What we know for sure is that PHP 7+ guarantees us cryptographically secure values with the random_bytes() function. The old code around openssl_random_pseudo_bytes() and mt_rand() in drupal_random_bytes() could be a security liability for us in the future, even on PHP 7+.

Now that PHP 5 is EOL for many years we could do a much simpler approach: if the function random_bytes() exists use that and return early. If not then keep using the old code around openssl_random_pseudo_bytes() unchanged as it is now. That way we leave PHP 5 installations open for potential security issues, but at least they can upgrade D7 core more smoothly and we don't intentionally break them. The result is a very simple patch here.

What do you think?

andypost’s picture

It could even be improved to support new in PHP 8.2 Random extension https://www.php.net/releases/8.2/en.php#random_extension

mfb’s picture

Here's a pretty simple patch to use random_bytes() if available. I didn't provide an interdiff since the preferred version hasn't yet been rerolled.

In case there are still any fans of PHP 5 and random_compat out there, it looks like there has only been one new version of random_compat released since I last commented in this issue one year ago (#217), so incorporating and chasing that library seems feasible - but I'd agree with @klausi in #223 that it just doesn't seem necessary at this point in time.

klausi’s picture

Status: Needs review » Reviewed & tested by the community

Perfect, this is exactly what I had in mind!

I queued a test run on PHP 5.3 to ensure it passes there as well. If that is green we should be good to go!

poker10’s picture

Thanks for the explanation @klausi and for the patch @mfb. This approach looks good!

  • mcdruid committed 6c1f518d on 7.x
    Issue #2550519 by pwolanin, klausi, nullkernel, cweagans, Heine,...
mcdruid’s picture

Status: Reviewed & tested by the community » Fixed

This is a great enhancement for D7 on modern PHP. Thanks everyone!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

joseph.olstad’s picture

Fantastic work! This approach is all of simple, elegant and productive all at once. Assists those of us that need it, without disrupting those that do not. Happiness for everyone! This is what I call a win for all. The best wins go unnoticed. Only those of us that are watching closely can see it.