Skip to content

Commit

Permalink
Merge pull request #1641 from fl4via/backport-fixes-2.2.x
Browse files Browse the repository at this point in the history
[UNDERTOW-2413][UNDERTOW-2382] CVE-2024-5971 CVE-2024-3653 Backport bug fixes
  • Loading branch information
fl4via authored Jul 16, 2024
2 parents ddc7904 + 6d674f0 commit cf404a1
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 37 deletions.
16 changes: 15 additions & 1 deletion core/src/main/java/io/undertow/Handlers.java
Original file line number Diff line number Diff line change
Expand Up @@ -555,14 +555,28 @@ public static ResponseRateLimitingHandler responseRateLimitingHandler(HttpHandle
* Creates a handler that automatically learns which resources to push based on the referer header
*
* @param maxEntries The maximum number of entries to store
* @param maxAge The maximum age of the entries
* @param maxAge The maximum age of the entries (ms)
* @param next The next handler
* @return A caching push handler
*/
public static LearningPushHandler learningPushHandler(int maxEntries, int maxAge, HttpHandler next) {
return new LearningPushHandler(maxEntries, maxAge, next);
}

/**
* Creates a handler that automatically learns which resources to push based on the referer header
*
* @param maxPathEntries The maximum number of entries to store. Path->{1..n Push}
* @param maxPathAge The maximum age of the entries (ms)
* @param maxPushEntries The maximum number of push entries stored for given path
* @param maxPushAge The maximum age of push entry (ms)
* @param next The next handler
* @return A caching push handler
*/
public static LearningPushHandler learningPushHandler(final int maxPathEntries, final int maxPathAge, final int maxPushEntries, final int maxPushAge, final HttpHandler next) {
return new LearningPushHandler(maxPathEntries, maxPathAge, maxPushEntries, maxPushAge, next);
}

/**
* A handler that sets response code but continues the exchange so the servlet's
* error page can be returned.
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/io/undertow/protocols/ssl/SslConduit.java
Original file line number Diff line number Diff line change
Expand Up @@ -1001,13 +1001,18 @@ private synchronized long doWrap(ByteBuffer[] userBuffers, int off, int len) thr

private SSLEngineResult wrapAndFlip(ByteBuffer[] userBuffers, int off, int len) throws IOException {
SSLEngineResult result = null;
int totalConsumedBytes = 0;
while (result == null || (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP
&& result.getStatus() != SSLEngineResult.Status.BUFFER_OVERFLOW && !engine.isInboundDone())) {
if (userBuffers == null) {
result = engine.wrap(EMPTY_BUFFER, wrappedData.getBuffer());
} else {
result = engine.wrap(userBuffers, off, len, wrappedData.getBuffer());
}
totalConsumedBytes += result.bytesConsumed();
}
if (totalConsumedBytes != result.bytesConsumed()) {
result = new SSLEngineResult(result.getStatus(), result.getHandshakeStatus(), totalConsumedBytes, result.bytesProduced());
}
wrappedData.getBuffer().flip();
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import io.undertow.util.Headers;
import io.undertow.util.Methods;

import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -47,20 +46,31 @@
public class LearningPushHandler implements HttpHandler {

private static final String SESSION_ATTRIBUTE = "io.undertow.PUSHED_RESOURCES";
private static final int DEFAULT_MAX_CACHE_ENTRIES = 1000;
private static final int DEFAULT_MAX_CACHE_AGE = -1;
private static final int DEFAULT_MAX_CACHE_ENTRIES = Integer.getInteger("io.undertow.handlers.learning-push.default-max-entries", 200);
private static final int DEFAULT_MAX_CACHE_AGE = Integer.getInteger("io.undertow.handlers.learning-push.default-max-age", LRUCache.MAX_AGE_NO_EXPIRY);

private final LRUCache<String, Map<String, PushedRequest>> cache;
private final LRUCache<String, LRUCache<String, PushedRequest>> cache;

private final HttpHandler next;
private int maxPushCacheEntries;
private int maxPushCacheAge;

public LearningPushHandler(final HttpHandler next) {
this(DEFAULT_MAX_CACHE_ENTRIES, DEFAULT_MAX_CACHE_AGE, next);
}

public LearningPushHandler(int maxEntries, int maxAge, HttpHandler next) {
public LearningPushHandler(int maxPathEtries, int maxPathAge, HttpHandler next) {
this.next = next;
cache = new LRUCache<>(maxEntries, maxAge);
this.maxPushCacheEntries = maxPathEtries;
this.maxPushCacheAge = maxPathAge;
cache = new LRUCache<>(maxPathEtries, maxPathAge);
}

public LearningPushHandler(int maxPathEtries, int maxPathAge, int maxPushEtries, int maxPushAge, HttpHandler next) {
this.next = next;
this.maxPushCacheEntries = maxPushEtries;
this.maxPushCacheAge = maxPushAge;
cache = new LRUCache<>(maxPathEtries, maxPathAge);
}

@Override
Expand Down Expand Up @@ -92,18 +102,21 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {

private void doPush(HttpServerExchange exchange, String fullPath) {
if (exchange.getConnection().isPushSupported()) {
Map<String, PushedRequest> toPush = cache.get(fullPath);
LRUCache<String, PushedRequest> toPush = cache.get(fullPath);
if (toPush != null) {
Session session = getSession(exchange);
if (session == null) {
return;
}
Map<String, Object> pushed = (Map<String, Object>) session.getAttribute(SESSION_ATTRIBUTE);
LRUCache<String, Object> pushed = (LRUCache<String, Object>) session.getAttribute(SESSION_ATTRIBUTE);
if (pushed == null) {
pushed = Collections.synchronizedMap(new HashMap<String, Object>());
pushed = new LRUCache<>(this.maxPushCacheEntries, this.maxPushCacheAge);
}
for (Map.Entry<String, PushedRequest> entry : toPush.entrySet()) {
PushedRequest request = entry.getValue();
for (String entryKey : toPush.keySet()) {
PushedRequest request = toPush.get(entryKey);
if(request == null) {
continue;
}
Object pushedKey = pushed.get(request.getPath());
boolean doPush = pushedKey == null;
if (!doPush) {
Expand All @@ -114,11 +127,12 @@ private void doPush(HttpServerExchange exchange, String fullPath) {
}
}
if (doPush) {
//pushResource will fill request headers.
exchange.getConnection().pushResource(request.getPath(), Methods.GET, request.getRequestHeaders());
if(request.getEtag() != null) {
pushed.put(request.getPath(), request.getEtag());
pushed.add(request.getPath(), request.getEtag());
} else {
pushed.put(request.getPath(), request.getLastModified());
pushed.add(request.getPath(), request.getLastModified());
}
}
}
Expand Down Expand Up @@ -166,16 +180,16 @@ public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener
lastModified = dt.getTime();
}
}
Map<String, PushedRequest> pushes = cache.get(referer);
LRUCache<String, PushedRequest> pushes = cache.get(referer);
if(pushes == null) {
synchronized (cache) {
pushes = cache.get(referer);
if(pushes == null) {
cache.add(referer, pushes = Collections.synchronizedMap(new HashMap<String, PushedRequest>()));
cache.add(referer, pushes = new LRUCache<String, LearningPushHandler.PushedRequest>(maxPushCacheEntries, maxPushCacheAge));
}
}
}
pushes.put(fullPath, new PushedRequest(new HeaderMap(), requestPath, etag, lastModified));
pushes.add(fullPath, new PushedRequest(new HeaderMap(), requestPath, etag, lastModified));
}

nextListener.proceed();
Expand Down Expand Up @@ -224,6 +238,8 @@ public Map<String, Class<?>> parameters() {
Map<String, Class<?>> params = new HashMap<>();
params.put("max-age", Integer.class);
params.put("max-entries", Integer.class);
params.put("max-push-age", Integer.class);
params.put("max-push-entries", Integer.class);
return params;
}

Expand All @@ -241,11 +257,12 @@ public String defaultParameter() {
public HandlerWrapper build(Map<String, Object> config) {
final int maxAge = config.containsKey("max-age") ? (Integer)config.get("max-age") : DEFAULT_MAX_CACHE_AGE;
final int maxEntries = config.containsKey("max-entries") ? (Integer)config.get("max-entries") : DEFAULT_MAX_CACHE_ENTRIES;

final int maxPushAge = config.containsKey("max-push-age") ? (Integer)config.get("max-push-age") : DEFAULT_MAX_CACHE_AGE;
final int maxPushEntries = config.containsKey("max-push-entries") ? (Integer)config.get("max-push-entries") : DEFAULT_MAX_CACHE_ENTRIES;
return new HandlerWrapper() {
@Override
public HttpHandler wrap(HttpHandler handler) {
return new LearningPushHandler(maxEntries, maxAge, handler);
return new LearningPushHandler(maxEntries, maxAge, maxPushEntries, maxPushAge, handler);
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public PathHandler() {

public PathHandler(int cacheSize) {
if(cacheSize > 0) {
cache = new LRUCache<>(cacheSize, -1, true);
cache = new LRUCache<>(cacheSize, LRUCache.MAX_AGE_NO_EXPIRY, true);
} else {
cache = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import java.util.concurrent.ConcurrentHashMap;

import io.undertow.UndertowLogger;
import io.undertow.server.handlers.resource.CachingResourceManager;
import io.undertow.util.ConcurrentDirectDeque;
import org.xnio.BufferAllocator;

Expand All @@ -49,6 +48,14 @@
*/
public class DirectBufferCache {
private static final int SAMPLE_INTERVAL = 5;
/**
* Max age 0, indicating that entries expire upon creation and are not retained;
*/
public static final int MAX_AGE_NO_CACHING = 0;
/**
* Mage age -1, entries dont expire
*/
public static final int MAX_AGE_NO_EXPIRY = -1;

private final LimitedBufferSlicePool pool;
private final ConcurrentMap<Object, CacheEntry> cache;
Expand Down Expand Up @@ -98,12 +105,12 @@ public CacheEntry get(Object key) {
}

final long expires = cacheEntry.getExpires();
if(expires == CachingResourceManager.MAX_AGE_NO_CACHING || (expires > 0 && System.currentTimeMillis() > expires)) {
if(expires == MAX_AGE_NO_CACHING || (expires > 0 && System.currentTimeMillis() > expires)) {
remove(key);
return null;
}

//either did not expire or CachingResourceManager.MAX_AGE_NO_EXPIRY
//either did not expire or MAX_AGE_NO_EXPIRY
if (cacheEntry.hit() % SAMPLE_INTERVAL == 0) {

bumpAccess(cacheEntry);
Expand Down Expand Up @@ -235,18 +242,18 @@ public boolean enabled() {
}

public void enable() {
if(this.maxAge == CachingResourceManager.MAX_AGE_NO_CACHING) {
this.expires = CachingResourceManager.MAX_AGE_NO_CACHING;
if(this.maxAge == MAX_AGE_NO_CACHING) {
this.expires = MAX_AGE_NO_CACHING;
disable();
} else if(this.maxAge == CachingResourceManager.MAX_AGE_NO_EXPIRY) {
this.expires = CachingResourceManager.MAX_AGE_NO_EXPIRY;
} else if(this.maxAge == MAX_AGE_NO_EXPIRY) {
this.expires = MAX_AGE_NO_EXPIRY;
this.enabled = 2;
} else if(this.maxAge > 0) {
this.expires = System.currentTimeMillis() + maxAge;
this.enabled = 2;
} else {
this.expires = CachingResourceManager.MAX_AGE_NO_CACHING;
UndertowLogger.ROOT_LOGGER.wrongCacheTTLValue(this.maxAge, CachingResourceManager.MAX_AGE_NO_CACHING);
this.expires = MAX_AGE_NO_CACHING;
UndertowLogger.ROOT_LOGGER.wrongCacheTTLValue(this.maxAge, MAX_AGE_NO_CACHING);
disable();
}
}
Expand Down
23 changes: 20 additions & 3 deletions core/src/main/java/io/undertow/server/handlers/cache/LRUCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

package io.undertow.server.handlers.cache;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
Expand All @@ -40,6 +42,14 @@
*/
public class LRUCache<K, V> {
private static final int SAMPLE_INTERVAL = 5;
/**
* Max age 0, indicating that entries expire upon creation and are not retained;
*/
public static final int MAX_AGE_NO_CACHING = 0;
/**
* Mage age -1, entries dont expire
*/
public static final int MAX_AGE_NO_EXPIRY = -1;

/**
* Max active entries that are present in the cache.
Expand All @@ -61,6 +71,7 @@ public LRUCache(int maxEntries, final int maxAge) {
this.maxEntries = maxEntries;
this.fifo = false;
}

public LRUCache(int maxEntries, final int maxAge, boolean fifo) {
this.maxAge = maxAge;
this.cache = new ConcurrentHashMap<>(16);
Expand All @@ -73,8 +84,10 @@ public void add(K key, V newValue) {
CacheEntry<K, V> value = cache.get(key);
if (value == null) {
long expires;
if(maxAge == -1) {
expires = -1;
if(maxAge == MAX_AGE_NO_EXPIRY) {
expires = MAX_AGE_NO_EXPIRY;
} else if (maxAge == MAX_AGE_NO_CACHING) {
return;
} else {
expires = System.currentTimeMillis() + maxAge;
}
Expand All @@ -101,7 +114,7 @@ public V get(K key) {
return null;
}
long expires = cacheEntry.getExpires();
if(expires != -1) {
if(expires != MAX_AGE_NO_EXPIRY) {
if(System.currentTimeMillis() > expires) {
remove(key);
return null;
Expand All @@ -117,6 +130,10 @@ public V get(K key) {
return cacheEntry.getValue();
}

public Set<K> keySet(){
return Collections.unmodifiableSet(this.cache.keySet());
}

private void bumpAccess(CacheEntry<K, V> cacheEntry) {
Object prevToken = cacheEntry.claimToken();
if (!Boolean.FALSE.equals(prevToken)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ public class CachingResourceManager implements ResourceManager {
/**
* Max age 0, indicating that entries expire upon creation and are not retained;
*/
public static final int MAX_AGE_NO_CACHING = 0;
public static final int MAX_AGE_NO_CACHING = LRUCache.MAX_AGE_NO_CACHING;
/**
* Mage age -1, this force manager to retain entries until underlying resource manager indicate that entries expired/changed
*/
public static final int MAX_AGE_NO_EXPIRY = -1;
public static final int MAX_AGE_NO_EXPIRY = LRUCache.MAX_AGE_NO_EXPIRY;
/**
* The biggest file size we cache
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ public ServletPathMatches(final Deployment deployment) {
this.deployment = deployment;
this.welcomePages = deployment.getDeploymentInfo().getWelcomePages().toArray(new String[deployment.getDeploymentInfo().getWelcomePages().size()]);
this.resourceManager = deployment.getDeploymentInfo().getResourceManager();
this.pathMatchCacheFixed = new LRUCache<>(1000, -1, true);
this.pathMatchCacheFixed = new LRUCache<>(1000, LRUCache.MAX_AGE_NO_EXPIRY, true);
this.pathMatchCacheResources = new LRUCache<>(1000,
resourceManager instanceof CachingResourceManager? ((CachingResourceManager) resourceManager).getMaxAge() : -1, true);
resourceManager instanceof CachingResourceManager ? ((CachingResourceManager) resourceManager).getMaxAge() : CachingResourceManager.MAX_AGE_NO_EXPIRY, true);
// add change listener for welcome pages
if (this.resourceManager.isResourceChangeListenerSupported()) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public ServletContextImpl(final ServletContainer servletContainer, final Deploym
this.attributes = deploymentInfo.getServletContextAttributeBackingMap();
}
attributes.putAll(deployment.getDeploymentInfo().getServletContextAttributes());
this.contentTypeCache = new LRUCache<>(deployment.getDeploymentInfo().getContentTypeCacheSize(), -1, true);
this.contentTypeCache = new LRUCache<>(deployment.getDeploymentInfo().getContentTypeCacheSize(), LRUCache.MAX_AGE_NO_EXPIRY, true);
this.defaultSessionTimeout = deploymentInfo.getDefaultSessionTimeout() / 60;
}

Expand Down

0 comments on commit cf404a1

Please sign in to comment.