Skip to content
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

Guard L2C regions against corrupted data #7778

Merged
merged 6 commits into from
Aug 14, 2019

Conversation

umpirsky
Copy link
Contributor

Fixes #7266.

@greg0ire
Copy link
Member

Please improve your commit message according to the contributing guide.

@umpirsky
Copy link
Contributor Author

@greg0ire Improved, thanks.

@greg0ire
Copy link
Member

Your now exemplary commit message says:

On cache miss the parameter $entityEntry sometimes will be false.

But apparently, false is not supposed to happen at all:

* @return CacheEntry[]|null The cached entries or NULL if one or more entries can not be found

IMO the right fix would be to make sure you cannot get false instead of handling it afterwards. Make the implementation conform to what the contract specified in the phpdoc says.

@umpirsky
Copy link
Contributor Author

I agree, this was a quick way for me to turn error into cache miss. But the root of the problem is in getMultiple() in deed, and I don't see the way to fix that.

@greg0ire
Copy link
Member

getMultiple()

Which implementation are we talking about here? Whatever it is, shouldn't we ensure it never returns false as an element of the returned array?

@umpirsky
Copy link
Contributor Author

umpirsky commented Aug 6, 2019

Which implementation are we talking about here? Whatever it is, shouldn't we ensure it never returns false as an element of the returned array?

It's RedisCache. It looks like this condition can allow false values if $this->redis->exists($key) is true.

@greg0ire
Copy link
Member

greg0ire commented Aug 7, 2019

RedisCache does not implement MultiGetRegion, does it? We're looking for an implementation of MultiGetRegion that returns a collection of CacheEntry objects, and lets a false slip in.

@greg0ire
Copy link
Member

greg0ire commented Aug 7, 2019

I think the issue might be here:

foreach ($keysToRetrieve as $index => $key) {
$returnableItems[$index] = $items[$key];
}

See the difference with the other implementation:

$entryKey = $this->getCacheEntryKey($key);
$entryValue = $this->cache->fetch($entryKey);
if ($entryValue === false) {
return null;
}

@umpirsky
Copy link
Contributor Author

umpirsky commented Aug 7, 2019

Yes, that looks wrong, it's worth trying.

@umpirsky
Copy link
Contributor Author

I can't simulate it in the test case:

--- a/tests/Doctrine/Tests/ORM/Cache/MultiGetRegionTest.php
+++ b/tests/Doctrine/Tests/ORM/Cache/MultiGetRegionTest.php
@@ -38,4 +38,26 @@ class MultiGetRegionTest extends AbstractRegionTest
         self::assertEquals($value1, $actual[0]);
         self::assertEquals($value2, $actual[1]);
     }
+
+    public function testGetMultiCacheMiss() : void
+    {
+        $key1   = new CacheKeyMock('key.1');
+        $value1 = new CacheEntryMock(['id' => 1, 'name' => 'bar']);
+
+        $key2   = new CacheKeyMock('key.2');
+        $value2 = new CacheEntryMock(['id' => 2, 'name' => 'bar']);
+
+        self::assertFalse($this->region->contains($key1));
+        self::assertFalse($this->region->contains($key2));
+
+        $this->region->put($key1, $value1);
+        $this->region->put($key2, $value2);
+
+        self::assertTrue($this->region->contains($key1));
+        self::assertTrue($this->region->contains($key2));
+
+        $actual = $this->region->getMultiple(new CollectionCacheEntry([$key1, $key2]));
+
+        self::assertNull($actual);
+    }

I don't know how to create CacheEntryMock in a way to simulate a cache miss. It also looks like this is prevented in https://github.com/doctrine/orm/blob/master/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php#L89-L91 so theoretically DefaultMultiGetRegion can't receive false value.

Am I missing something?

@lcobucci
Copy link
Member

@umpirsky I think we're looking this from the wrong angle... I mean, if the key exists in Redis it means that it was stored as false instead of the expected object and we should look into the write side instead.

@lcobucci
Copy link
Member

Also, please send the bugfix to 2.6 branch instead of master. We'll handle the porting :)

@lcobucci lcobucci added this to the 2.6.4 milestone Aug 13, 2019
@umpirsky
Copy link
Contributor Author

That's the problem, when I try to create new CacheEntryMock(false) I get InvalidArgumentException: Passed variable is not an array or object. That's why I can't create failing test.

@lcobucci
Copy link
Member

@umpirsky I was looking at the write side and had some thoughts that might be related to max value size for strings or serialisation issues... which redis and redis client versions are you using and do you have igbinary installed?

@lcobucci lcobucci changed the base branch from master to 2.6 August 13, 2019 18:37
@lcobucci
Copy link
Member

@umpirsky I just rebased your branch so that we can make this part of the next bugfix release. I'll continue investigating things here.

@lcobucci lcobucci changed the title Fix issue #7266 Guard L2C regions against corrupted data Aug 13, 2019
lcobucci
lcobucci previously approved these changes Aug 13, 2019
Copy link
Member

@lcobucci lcobucci left a comment

Choose a reason for hiding this comment

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

I'd say things are alright now, although this change doesn't fix the generation of such corrupted data (which I couldn't really find 😞).

It would be nice to get another pair of eyes here, just to avoid potential issues.

@umpirsky thanks for your effort and time 👍

@lcobucci lcobucci requested a review from greg0ire August 13, 2019 20:52
@lcobucci lcobucci added the Bug label Aug 13, 2019
@lcobucci lcobucci assigned lcobucci and unassigned umpirsky Aug 13, 2019
Copy link
Member

@guilhermeblanco guilhermeblanco left a comment

Choose a reason for hiding this comment

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

Patch seems fine for me aside from the asserts. They should be either removed or try/catch'ed.

@lcobucci
Copy link
Member

lcobucci commented Aug 14, 2019

@guilhermeblanco these are meant for development only and replace the not so useful doc blocks for variables. PHP production config doesn't even generate opcodes for them.

There's an extensive discussion on that in doctrine/coding-standard#47

@lcobucci lcobucci dismissed guilhermeblanco’s stale review August 14, 2019 15:42

As discussed IRL these assertions are fine

umpirsky and others added 6 commits August 14, 2019 17:42
The following mistakes occur occasionally:

```
Call to a member function resolveAssociationEntries() on boolean {"detail":"[object] (Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Call to a member function resolveAssociationEntries() on boolean at /www/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/DefaultQueryCache.php:140)"}
```

On cache miss the parameter `$entityEntry` sometimes will be false. This fixes issue doctrine#7266.
To adhere to our coding standard.
For some bizarre reason the underlying cache drivers are returning
unexpected values, which are leaking to the cache objects and causing
them to error.

This makes our cache regions much more strict about the types that are
fetched from the cache provider, ensuring that no invalid information is
ever sent to the hydrators.
@lcobucci lcobucci merged commit 642e543 into doctrine:2.6 Aug 14, 2019
@umpirsky umpirsky deleted the fix/issue-7266 branch August 28, 2019 10:16
@umpirsky
Copy link
Contributor Author

Will this be released in 2.6.4 or 2.7?

@lcobucci
Copy link
Member

lcobucci commented Aug 28, 2019

2.6.4 since it's a bug fix. I plan to release it this weekend.

@umpirsky
Copy link
Contributor Author

umpirsky commented Sep 5, 2019

Next weekend I guess? :)

@lcobucci
Copy link
Member

lcobucci commented Sep 9, 2019

@umpirsky sorry, life happened and didn't allow me to handle things. Will do it ASAP (but won't promise any date 😞)

Voziv added a commit to ratehub/doctrine2 that referenced this pull request Oct 22, 2019
v2.6.4

[![Build Status](https://travis-ci.org/doctrine/orm.svg?branch=v2.6.4)](https://travis-ci.org/doctrine/orm)

In this release we've fixes many bugs (including a performance regression) and
made the v2.x series compatible with PHP 7.4.

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

- Total issues resolved: **11**
- Total pull requests resolved: **32**
- Total contributors: **30**

Improvement
-----------

 - [7785: Fix "access array offset on value of type null" PHP 7.4 notices](doctrine#7785) thanks to @mlocati
 - [7142: Rename this repository to doctrine/orm](doctrine#7142) thanks to @greg0ire

Bug
------------------

 - [7821: Bug: doctrine#7820 paginator ignores dbal type conversions in identifiers](doctrine#7821) thanks to @Ocramius
 - [7778: Guard L2C regions against corrupted data](doctrine#7778) thanks to @umpirsky
 - [7767: PersistentCollection::matching() does not respect the collections native sorting](doctrine#7767) thanks to @stephanschuler
 - [7766: Respect collection orderBy meta when matching()](doctrine#7766) thanks to @stephanschuler
 - [7761: Do not modify UOW on PersistentCollection::clear() when owner has DEFFERED&doctrine#95;EXPLICIT change tracking policy](doctrine#7761) thanks to @paxal
 - [7750: Fix incorrect return of null values in L2C](doctrine#7750) thanks to @AlexSmerw
 - [7737: Fix MEMBER&doctrine#95;OF comparison when using criteria in query builder](doctrine#7737) thanks to @Smartel1
 - [7735: Null in fields value in Cached Entity several times on day on high-load project.](doctrine#7735) thanks to @AlexSmerw
 - [7630: Fix doctrine#7629 - `scheduledForSynchronization` leaks memory when using `@ORM\ChangeTrackingPolicy("DEFERRED&doctrine#95;EXPLICIT")`](doctrine#7630) thanks to @yethee
 - [7528: Prevent `UnitOfWork` lookup for DBAL types specified in `Doctrine\ORM\Query#setParameter()`](doctrine#7528) thanks to @Ocramius
 - [7322: JoinedSubclassPersister pass identifier types on delete](doctrine#7322) thanks to @dennisenderink and @fred-jan
 - [7266: Call to a member function resolveAssociationEntries() on boolean {"detail":"&doctrine#91;object&doctrine#93; (Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Call to a member function resolveAssociationEntries() on boolean at /www/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/DefaultQueryCache.php:140)"}](doctrine#7266) thanks to @mingmingxianseng
 - [4632: DDC-3789: Paginator does not convert entity ids if they are value objects](doctrine#4632) thanks to @doctrinebot

Documentation
-------------

 - [7818: Add note into docs about not using SimpleAnnotationReader](doctrine#7818) thanks to @SenseException
 - [7791: Fix preFlush event documentation stating incorrectly that flush can be called safely](doctrine#7791) thanks to @Steveb-p
 - [7753: Add ORM annotations in getting-started docs](doctrine#7753) thanks to @SenseException and @wajdijurry
 - [7744: Fixed a typo-error](doctrine#7744) thanks to @noobshow
 - [7732: &doctrine#91;Documentation&doctrine#93; Missing comma fix](doctrine#7732) thanks to @lchrusciel
 - [7729: Update DATE&doctrine#95;ADD and DATE&doctrine#95;SUB docs](doctrine#7729) thanks to @JoppeDC
 - [7672: Added cross-links to relevant documentation](doctrine#7672) thanks to @jschaedl
 - [7612: Update ordered-associations.rst](doctrine#7612) thanks to @spirlici
 - [7610: Change APC to OPcache in improving-performance.rst ](doctrine#7610) thanks to @smtchahal
 - [7596: Correct method names and broken link in docs](doctrine#7596) thanks to @mbessolov
 - [7577: Fix of single link to dbal docs in advanced-configuration.rst](doctrine#7577) thanks to @SenseException
 - [7572: Remove codeigniter Framework example](doctrine#7572) thanks to @SenseException
 - [7571: Fix typo in inheritance mappings docs](doctrine#7571) thanks to @batwolf
 - [7557: Change Stackoverflow tag to doctrine-orm](doctrine#7557) thanks to @malarzm
 - [7551: &doctrine#91;2.6&doctrine#93; Migrate repository name doctrine/doctrine2 -> doctrine/orm](doctrine#7551) thanks to @Majkl578
 - [7530: Documentation error typo fix: s/Used-defined/User-Defined](doctrine#7530) thanks to @vladyslavstartsev
 - [7519: doctrine#7518 Fixed type mismatch between `EntityRepository#&doctrine#95;&doctrine#95;construct()` and its documented constructor arguments](doctrine#7519) thanks to @koftikes
 - [7518: `EntityRepository::&doctrine#95;&doctrine#95;construct()` expects `Doctrine\ORM\EntityManager` instead of actual required `EntityManagerInterface`](doctrine#7518) thanks to @koftikes
 - [7490: Fix broken link](doctrine#7490) thanks to @vladyslavstartsev
 - [7483: Fixed a minor syntax issue](doctrine#7483) thanks to @javiereguiluz

CI
-----------------

 - [7794: Fix test compatibility with DBAL 2.10.x-dev](doctrine#7794) thanks to @lcobucci
 - [7731: Replace custom install script with add-on](doctrine#7731) thanks to @greg0ire
 - [7473: Incremental CS checks in 2.x branches](doctrine#7473) thanks to @Majkl578
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants