Skip to content

Commit cf404a1

Browse files
authored
Merge pull request #1641 from fl4via/backport-fixes-2.2.x
[UNDERTOW-2413][UNDERTOW-2382] CVE-2024-5971 CVE-2024-3653 Backport bug fixes
2 parents ddc7904 + 6d674f0 commit cf404a1

File tree

9 files changed

+97
-37
lines changed

9 files changed

+97
-37
lines changed

core/src/main/java/io/undertow/Handlers.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,14 +555,28 @@ public static ResponseRateLimitingHandler responseRateLimitingHandler(HttpHandle
555555
* Creates a handler that automatically learns which resources to push based on the referer header
556556
*
557557
* @param maxEntries The maximum number of entries to store
558-
* @param maxAge The maximum age of the entries
558+
* @param maxAge The maximum age of the entries (ms)
559559
* @param next The next handler
560560
* @return A caching push handler
561561
*/
562562
public static LearningPushHandler learningPushHandler(int maxEntries, int maxAge, HttpHandler next) {
563563
return new LearningPushHandler(maxEntries, maxAge, next);
564564
}
565565

566+
/**
567+
* Creates a handler that automatically learns which resources to push based on the referer header
568+
*
569+
* @param maxPathEntries The maximum number of entries to store. Path->{1..n Push}
570+
* @param maxPathAge The maximum age of the entries (ms)
571+
* @param maxPushEntries The maximum number of push entries stored for given path
572+
* @param maxPushAge The maximum age of push entry (ms)
573+
* @param next The next handler
574+
* @return A caching push handler
575+
*/
576+
public static LearningPushHandler learningPushHandler(final int maxPathEntries, final int maxPathAge, final int maxPushEntries, final int maxPushAge, final HttpHandler next) {
577+
return new LearningPushHandler(maxPathEntries, maxPathAge, maxPushEntries, maxPushAge, next);
578+
}
579+
566580
/**
567581
* A handler that sets response code but continues the exchange so the servlet's
568582
* error page can be returned.

core/src/main/java/io/undertow/protocols/ssl/SslConduit.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,13 +1001,18 @@ private synchronized long doWrap(ByteBuffer[] userBuffers, int off, int len) thr
10011001

10021002
private SSLEngineResult wrapAndFlip(ByteBuffer[] userBuffers, int off, int len) throws IOException {
10031003
SSLEngineResult result = null;
1004+
int totalConsumedBytes = 0;
10041005
while (result == null || (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP
10051006
&& result.getStatus() != SSLEngineResult.Status.BUFFER_OVERFLOW && !engine.isInboundDone())) {
10061007
if (userBuffers == null) {
10071008
result = engine.wrap(EMPTY_BUFFER, wrappedData.getBuffer());
10081009
} else {
10091010
result = engine.wrap(userBuffers, off, len, wrappedData.getBuffer());
10101011
}
1012+
totalConsumedBytes += result.bytesConsumed();
1013+
}
1014+
if (totalConsumedBytes != result.bytesConsumed()) {
1015+
result = new SSLEngineResult(result.getStatus(), result.getHandshakeStatus(), totalConsumedBytes, result.bytesProduced());
10111016
}
10121017
wrappedData.getBuffer().flip();
10131018
return result;

core/src/main/java/io/undertow/server/handlers/LearningPushHandler.java

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import io.undertow.util.Headers;
3333
import io.undertow.util.Methods;
3434

35-
import java.util.Collections;
3635
import java.util.Date;
3736
import java.util.HashMap;
3837
import java.util.Map;
@@ -47,20 +46,31 @@
4746
public class LearningPushHandler implements HttpHandler {
4847

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

53-
private final LRUCache<String, Map<String, PushedRequest>> cache;
52+
private final LRUCache<String, LRUCache<String, PushedRequest>> cache;
5453

5554
private final HttpHandler next;
55+
private int maxPushCacheEntries;
56+
private int maxPushCacheAge;
5657

5758
public LearningPushHandler(final HttpHandler next) {
5859
this(DEFAULT_MAX_CACHE_ENTRIES, DEFAULT_MAX_CACHE_AGE, next);
5960
}
6061

61-
public LearningPushHandler(int maxEntries, int maxAge, HttpHandler next) {
62+
public LearningPushHandler(int maxPathEtries, int maxPathAge, HttpHandler next) {
6263
this.next = next;
63-
cache = new LRUCache<>(maxEntries, maxAge);
64+
this.maxPushCacheEntries = maxPathEtries;
65+
this.maxPushCacheAge = maxPathAge;
66+
cache = new LRUCache<>(maxPathEtries, maxPathAge);
67+
}
68+
69+
public LearningPushHandler(int maxPathEtries, int maxPathAge, int maxPushEtries, int maxPushAge, HttpHandler next) {
70+
this.next = next;
71+
this.maxPushCacheEntries = maxPushEtries;
72+
this.maxPushCacheAge = maxPushAge;
73+
cache = new LRUCache<>(maxPathEtries, maxPathAge);
6474
}
6575

6676
@Override
@@ -92,18 +102,21 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
92102

93103
private void doPush(HttpServerExchange exchange, String fullPath) {
94104
if (exchange.getConnection().isPushSupported()) {
95-
Map<String, PushedRequest> toPush = cache.get(fullPath);
105+
LRUCache<String, PushedRequest> toPush = cache.get(fullPath);
96106
if (toPush != null) {
97107
Session session = getSession(exchange);
98108
if (session == null) {
99109
return;
100110
}
101-
Map<String, Object> pushed = (Map<String, Object>) session.getAttribute(SESSION_ATTRIBUTE);
111+
LRUCache<String, Object> pushed = (LRUCache<String, Object>) session.getAttribute(SESSION_ATTRIBUTE);
102112
if (pushed == null) {
103-
pushed = Collections.synchronizedMap(new HashMap<String, Object>());
113+
pushed = new LRUCache<>(this.maxPushCacheEntries, this.maxPushCacheAge);
104114
}
105-
for (Map.Entry<String, PushedRequest> entry : toPush.entrySet()) {
106-
PushedRequest request = entry.getValue();
115+
for (String entryKey : toPush.keySet()) {
116+
PushedRequest request = toPush.get(entryKey);
117+
if(request == null) {
118+
continue;
119+
}
107120
Object pushedKey = pushed.get(request.getPath());
108121
boolean doPush = pushedKey == null;
109122
if (!doPush) {
@@ -114,11 +127,12 @@ private void doPush(HttpServerExchange exchange, String fullPath) {
114127
}
115128
}
116129
if (doPush) {
130+
//pushResource will fill request headers.
117131
exchange.getConnection().pushResource(request.getPath(), Methods.GET, request.getRequestHeaders());
118132
if(request.getEtag() != null) {
119-
pushed.put(request.getPath(), request.getEtag());
133+
pushed.add(request.getPath(), request.getEtag());
120134
} else {
121-
pushed.put(request.getPath(), request.getLastModified());
135+
pushed.add(request.getPath(), request.getLastModified());
122136
}
123137
}
124138
}
@@ -166,16 +180,16 @@ public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener
166180
lastModified = dt.getTime();
167181
}
168182
}
169-
Map<String, PushedRequest> pushes = cache.get(referer);
183+
LRUCache<String, PushedRequest> pushes = cache.get(referer);
170184
if(pushes == null) {
171185
synchronized (cache) {
172186
pushes = cache.get(referer);
173187
if(pushes == null) {
174-
cache.add(referer, pushes = Collections.synchronizedMap(new HashMap<String, PushedRequest>()));
188+
cache.add(referer, pushes = new LRUCache<String, LearningPushHandler.PushedRequest>(maxPushCacheEntries, maxPushCacheAge));
175189
}
176190
}
177191
}
178-
pushes.put(fullPath, new PushedRequest(new HeaderMap(), requestPath, etag, lastModified));
192+
pushes.add(fullPath, new PushedRequest(new HeaderMap(), requestPath, etag, lastModified));
179193
}
180194

181195
nextListener.proceed();
@@ -224,6 +238,8 @@ public Map<String, Class<?>> parameters() {
224238
Map<String, Class<?>> params = new HashMap<>();
225239
params.put("max-age", Integer.class);
226240
params.put("max-entries", Integer.class);
241+
params.put("max-push-age", Integer.class);
242+
params.put("max-push-entries", Integer.class);
227243
return params;
228244
}
229245

@@ -241,11 +257,12 @@ public String defaultParameter() {
241257
public HandlerWrapper build(Map<String, Object> config) {
242258
final int maxAge = config.containsKey("max-age") ? (Integer)config.get("max-age") : DEFAULT_MAX_CACHE_AGE;
243259
final int maxEntries = config.containsKey("max-entries") ? (Integer)config.get("max-entries") : DEFAULT_MAX_CACHE_ENTRIES;
244-
260+
final int maxPushAge = config.containsKey("max-push-age") ? (Integer)config.get("max-push-age") : DEFAULT_MAX_CACHE_AGE;
261+
final int maxPushEntries = config.containsKey("max-push-entries") ? (Integer)config.get("max-push-entries") : DEFAULT_MAX_CACHE_ENTRIES;
245262
return new HandlerWrapper() {
246263
@Override
247264
public HttpHandler wrap(HttpHandler handler) {
248-
return new LearningPushHandler(maxEntries, maxAge, handler);
265+
return new LearningPushHandler(maxEntries, maxAge, maxPushEntries, maxPushAge, handler);
249266
}
250267
};
251268
}

core/src/main/java/io/undertow/server/handlers/PathHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public PathHandler() {
5959

6060
public PathHandler(int cacheSize) {
6161
if(cacheSize > 0) {
62-
cache = new LRUCache<>(cacheSize, -1, true);
62+
cache = new LRUCache<>(cacheSize, LRUCache.MAX_AGE_NO_EXPIRY, true);
6363
} else {
6464
cache = null;
6565
}

core/src/main/java/io/undertow/server/handlers/cache/DirectBufferCache.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import java.util.concurrent.ConcurrentHashMap;
3030

3131
import io.undertow.UndertowLogger;
32-
import io.undertow.server.handlers.resource.CachingResourceManager;
3332
import io.undertow.util.ConcurrentDirectDeque;
3433
import org.xnio.BufferAllocator;
3534

@@ -49,6 +48,14 @@
4948
*/
5049
public class DirectBufferCache {
5150
private static final int SAMPLE_INTERVAL = 5;
51+
/**
52+
* Max age 0, indicating that entries expire upon creation and are not retained;
53+
*/
54+
public static final int MAX_AGE_NO_CACHING = 0;
55+
/**
56+
* Mage age -1, entries dont expire
57+
*/
58+
public static final int MAX_AGE_NO_EXPIRY = -1;
5259

5360
private final LimitedBufferSlicePool pool;
5461
private final ConcurrentMap<Object, CacheEntry> cache;
@@ -98,12 +105,12 @@ public CacheEntry get(Object key) {
98105
}
99106

100107
final long expires = cacheEntry.getExpires();
101-
if(expires == CachingResourceManager.MAX_AGE_NO_CACHING || (expires > 0 && System.currentTimeMillis() > expires)) {
108+
if(expires == MAX_AGE_NO_CACHING || (expires > 0 && System.currentTimeMillis() > expires)) {
102109
remove(key);
103110
return null;
104111
}
105112

106-
//either did not expire or CachingResourceManager.MAX_AGE_NO_EXPIRY
113+
//either did not expire or MAX_AGE_NO_EXPIRY
107114
if (cacheEntry.hit() % SAMPLE_INTERVAL == 0) {
108115

109116
bumpAccess(cacheEntry);
@@ -235,18 +242,18 @@ public boolean enabled() {
235242
}
236243

237244
public void enable() {
238-
if(this.maxAge == CachingResourceManager.MAX_AGE_NO_CACHING) {
239-
this.expires = CachingResourceManager.MAX_AGE_NO_CACHING;
245+
if(this.maxAge == MAX_AGE_NO_CACHING) {
246+
this.expires = MAX_AGE_NO_CACHING;
240247
disable();
241-
} else if(this.maxAge == CachingResourceManager.MAX_AGE_NO_EXPIRY) {
242-
this.expires = CachingResourceManager.MAX_AGE_NO_EXPIRY;
248+
} else if(this.maxAge == MAX_AGE_NO_EXPIRY) {
249+
this.expires = MAX_AGE_NO_EXPIRY;
243250
this.enabled = 2;
244251
} else if(this.maxAge > 0) {
245252
this.expires = System.currentTimeMillis() + maxAge;
246253
this.enabled = 2;
247254
} else {
248-
this.expires = CachingResourceManager.MAX_AGE_NO_CACHING;
249-
UndertowLogger.ROOT_LOGGER.wrongCacheTTLValue(this.maxAge, CachingResourceManager.MAX_AGE_NO_CACHING);
255+
this.expires = MAX_AGE_NO_CACHING;
256+
UndertowLogger.ROOT_LOGGER.wrongCacheTTLValue(this.maxAge, MAX_AGE_NO_CACHING);
250257
disable();
251258
}
252259
}

core/src/main/java/io/undertow/server/handlers/cache/LRUCache.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
package io.undertow.server.handlers.cache;
2020

21+
import java.util.Collections;
22+
import java.util.Set;
2123
import java.util.concurrent.ConcurrentHashMap;
2224
import java.util.concurrent.ConcurrentMap;
2325
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
@@ -40,6 +42,14 @@
4042
*/
4143
public class LRUCache<K, V> {
4244
private static final int SAMPLE_INTERVAL = 5;
45+
/**
46+
* Max age 0, indicating that entries expire upon creation and are not retained;
47+
*/
48+
public static final int MAX_AGE_NO_CACHING = 0;
49+
/**
50+
* Mage age -1, entries dont expire
51+
*/
52+
public static final int MAX_AGE_NO_EXPIRY = -1;
4353

4454
/**
4555
* Max active entries that are present in the cache.
@@ -61,6 +71,7 @@ public LRUCache(int maxEntries, final int maxAge) {
6171
this.maxEntries = maxEntries;
6272
this.fifo = false;
6373
}
74+
6475
public LRUCache(int maxEntries, final int maxAge, boolean fifo) {
6576
this.maxAge = maxAge;
6677
this.cache = new ConcurrentHashMap<>(16);
@@ -73,8 +84,10 @@ public void add(K key, V newValue) {
7384
CacheEntry<K, V> value = cache.get(key);
7485
if (value == null) {
7586
long expires;
76-
if(maxAge == -1) {
77-
expires = -1;
87+
if(maxAge == MAX_AGE_NO_EXPIRY) {
88+
expires = MAX_AGE_NO_EXPIRY;
89+
} else if (maxAge == MAX_AGE_NO_CACHING) {
90+
return;
7891
} else {
7992
expires = System.currentTimeMillis() + maxAge;
8093
}
@@ -101,7 +114,7 @@ public V get(K key) {
101114
return null;
102115
}
103116
long expires = cacheEntry.getExpires();
104-
if(expires != -1) {
117+
if(expires != MAX_AGE_NO_EXPIRY) {
105118
if(System.currentTimeMillis() > expires) {
106119
remove(key);
107120
return null;
@@ -117,6 +130,10 @@ public V get(K key) {
117130
return cacheEntry.getValue();
118131
}
119132

133+
public Set<K> keySet(){
134+
return Collections.unmodifiableSet(this.cache.keySet());
135+
}
136+
120137
private void bumpAccess(CacheEntry<K, V> cacheEntry) {
121138
Object prevToken = cacheEntry.claimToken();
122139
if (!Boolean.FALSE.equals(prevToken)) {

core/src/main/java/io/undertow/server/handlers/resource/CachingResourceManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ public class CachingResourceManager implements ResourceManager {
3434
/**
3535
* Max age 0, indicating that entries expire upon creation and are not retained;
3636
*/
37-
public static final int MAX_AGE_NO_CACHING = 0;
37+
public static final int MAX_AGE_NO_CACHING = LRUCache.MAX_AGE_NO_CACHING;
3838
/**
3939
* Mage age -1, this force manager to retain entries until underlying resource manager indicate that entries expired/changed
4040
*/
41-
public static final int MAX_AGE_NO_EXPIRY = -1;
41+
public static final int MAX_AGE_NO_EXPIRY = LRUCache.MAX_AGE_NO_EXPIRY;
4242
/**
4343
* The biggest file size we cache
4444
*/

servlet/src/main/java/io/undertow/servlet/handlers/ServletPathMatches.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ public ServletPathMatches(final Deployment deployment) {
7979
this.deployment = deployment;
8080
this.welcomePages = deployment.getDeploymentInfo().getWelcomePages().toArray(new String[deployment.getDeploymentInfo().getWelcomePages().size()]);
8181
this.resourceManager = deployment.getDeploymentInfo().getResourceManager();
82-
this.pathMatchCacheFixed = new LRUCache<>(1000, -1, true);
82+
this.pathMatchCacheFixed = new LRUCache<>(1000, LRUCache.MAX_AGE_NO_EXPIRY, true);
8383
this.pathMatchCacheResources = new LRUCache<>(1000,
84-
resourceManager instanceof CachingResourceManager? ((CachingResourceManager) resourceManager).getMaxAge() : -1, true);
84+
resourceManager instanceof CachingResourceManager ? ((CachingResourceManager) resourceManager).getMaxAge() : CachingResourceManager.MAX_AGE_NO_EXPIRY, true);
8585
// add change listener for welcome pages
8686
if (this.resourceManager.isResourceChangeListenerSupported()) {
8787
try {

servlet/src/main/java/io/undertow/servlet/spec/ServletContextImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public ServletContextImpl(final ServletContainer servletContainer, final Deploym
145145
this.attributes = deploymentInfo.getServletContextAttributeBackingMap();
146146
}
147147
attributes.putAll(deployment.getDeploymentInfo().getServletContextAttributes());
148-
this.contentTypeCache = new LRUCache<>(deployment.getDeploymentInfo().getContentTypeCacheSize(), -1, true);
148+
this.contentTypeCache = new LRUCache<>(deployment.getDeploymentInfo().getContentTypeCacheSize(), LRUCache.MAX_AGE_NO_EXPIRY, true);
149149
this.defaultSessionTimeout = deploymentInfo.getDefaultSessionTimeout() / 60;
150150
}
151151

0 commit comments

Comments
 (0)