Skip to content

Migrate to Ehcache 3 with hash-flooding DoS protection#277

Merged
hazendaz merged 4 commits intomasterfrom
copilot/update-ehcache-integration
Mar 2, 2026
Merged

Migrate to Ehcache 3 with hash-flooding DoS protection#277
hazendaz merged 4 commits intomasterfrom
copilot/update-ehcache-integration

Conversation

Copy link
Contributor

Copilot AI commented Feb 28, 2026

Replaces the EOL net.sf.ehcache:ehcache:2.10.9.2 dependency with org.ehcache:ehcache:3.10.8 and addresses the hash-flooding DoS vulnerability inherent in Ehcache 3's use of raw hashCode() in its internal hash structures.

Dependency change

  • net.sf.ehcache:ehcache:2.10.9.2org.ehcache:ehcache:3.10.8
  • Excludes jaxb-runtime (only needed for XML-based Ehcache config; its transitive deps resolve from blocked HTTP repositories)

API migration (AbstractEhcacheCache, EhcacheCache, EhBlockingCache)

  • Ehcache 3 config is immutable after creation — cache is now created lazily on first use, allowing MyBatis property setters (setTimeToLiveSeconds, setMaxEntriesLocalHeap, etc.) to be applied beforehand; config changes after first use atomically recreate the cache
  • Null values rejected by Ehcache 3 — stored as a NullValue sentinel, transparently unwrapped on read
  • getSize() via iteration (Ehcache 3's Cache has no size() method)
  • EhBlockingCache.removeObject now calls super.removeObject() instead of cache.put(key, null); Ehcache 3 has no BlockingCache equivalent so blocking must be provided by the caller (e.g. MyBatis's BlockingCache decorator)

Hash-flooding DoS protection (HashKeyWrapper)

Ehcache 3 uses raw key.hashCode() in its internal hash structures with no spreading. On a bounded cache an attacker who controls query parameters (which feed MyBatis's CacheKey) can craft keys that all collide in the same bucket, degrading every operation from O(1) to O(n).

HashKeyWrapper wraps every key and applies the Murmur3 fmix32 finalizer before the value reaches Ehcache:

// AbstractEhcacheCache — all three key-bearing call sites
getOrCreateCache().get(new HashKeyWrapper(key));
getOrCreateCache().put(new HashKeyWrapper(key), ...);
getOrCreateCache().remove(new HashKeyWrapper(key));
// fmix32: near-perfect avalanche — ~50% of output bits flip per 1-bit input change
static int fmix32(int h) {
    h ^= h >>> 16;  h *= 0x85ebca6b;
    h ^= h >>> 13;  h *= 0xc2b2ae35;
    return h ^ (h >>> 16);
}

HashKeyWrapper is Serializable, handles null keys, and is transparent to callers — equals() delegates to the wrapped key.

ObjectSerializer

New Serializer<Object> backed by standard Java serialization, available for configurations that add off-heap or disk tiers.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits February 28, 2026 20:14
Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
Copilot AI changed the title [WIP] Update integration for ehcache 3 support Migrate mybatis-ehcache from Ehcache 2 to Ehcache 3 Feb 28, 2026
public int getSize() {
return cache.getSize();
int count = 0;
for (org.ehcache.Cache.Entry<Object, Object> ignored : getOrCreateCache()) {

Check notice

Code scanning / CodeQL

Unread local variable Note

Variable 'Entry<Object,Object> ignored' is never read.

Copilot Autofix

AI 14 days ago

In general, to fix an unread local variable in a loop, either remove the variable if possible or restructure the loop so that iteration does not require a throwaway variable. When using an enhanced for-loop, Java requires a loop variable, so avoiding an unused variable means not using the enhanced for-loop in cases where the element is not needed.

Here, the method getSize() only needs to count the number of entries in the Ehcache; it never uses the Entry objects themselves. The best fix without changing functionality is to replace the enhanced for-loop with an explicit Iterator over the cache, using hasNext() to drive the loop. This avoids declaring an unused variable while preserving the existing counting behavior.

Concretely, in src/main/java/org/mybatis/caches/ehcache/AbstractEhcacheCache.java, lines 143–148 should be updated so that:

  • The enhanced for-loop for (org.ehcache.Cache.Entry<Object, Object> ignored : getOrCreateCache()) { ... } is replaced with creation of a java.util.Iterator<?> from getOrCreateCache().iterator().
  • A while (iterator.hasNext()) { ... } loop increments count once per element without ever binding the element to a local variable.
  • No new imports are required because we can refer to java.util.Iterator by its fully qualified name.

This keeps the method semantics identical (iterating over all entries and counting them) while eliminating the unread local variable.

Suggested changeset 1
src/main/java/org/mybatis/caches/ehcache/AbstractEhcacheCache.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/mybatis/caches/ehcache/AbstractEhcacheCache.java b/src/main/java/org/mybatis/caches/ehcache/AbstractEhcacheCache.java
--- a/src/main/java/org/mybatis/caches/ehcache/AbstractEhcacheCache.java
+++ b/src/main/java/org/mybatis/caches/ehcache/AbstractEhcacheCache.java
@@ -142,7 +142,9 @@
   @Override
   public int getSize() {
     int count = 0;
-    for (org.ehcache.Cache.Entry<Object, Object> ignored : getOrCreateCache()) {
+    java.util.Iterator<?> iterator = getOrCreateCache().iterator();
+    while (iterator.hasNext()) {
+      iterator.next();
       count++;
     }
     return count;
EOF
@@ -142,7 +142,9 @@
@Override
public int getSize() {
int count = 0;
for (org.ehcache.Cache.Entry<Object, Object> ignored : getOrCreateCache()) {
java.util.Iterator<?> iterator = getOrCreateCache().iterator();
while (iterator.hasNext()) {
iterator.next();
count++;
}
return count;
Copilot is powered by AI and may make mistakes. Always verify output.
Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
Copilot AI changed the title Migrate mybatis-ehcache from Ehcache 2 to Ehcache 3 Migrate to Ehcache 3 with hash-flooding DoS protection Feb 28, 2026
@hazendaz hazendaz marked this pull request as ready for review February 28, 2026 21:31
* @see <a href="https://github.com/jhipster/generator-jhipster/issues/28546">jhipster/generator-jhipster #28546</a>
* @see <a href="https://github.com/mybatis/ehcache-cache/issues/61">mybatis/ehcache-cache #61</a>
*/
final class HashKeyWrapper implements Serializable {

Choose a reason for hiding this comment

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

if you restrict the minimum to v3.11.1 then this should be resolved for you.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks @ben-manes, since users can override, I presume its otherwise fine to still be present regardless?

Choose a reason for hiding this comment

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

Sure no harm including if you prefer

@hazendaz hazendaz merged commit 03c5565 into master Mar 2, 2026
22 checks passed
@hazendaz hazendaz deleted the copilot/update-ehcache-integration branch March 7, 2026 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants