CVE-2022-39368 (published 2022-11-10T00:15Z)

Eclipse Californium is a Java implementation of RFC7252 - Constrained Application Protocol for IoT Cloud services. In versions prior to 3.7.0, and 2.7.4, Californium is vulnerable to a Denial of Service. Failing handshakes don't cleanup counters for throttling, causing the threshold to be reached without being released again. This results in permanently dropping records. The issue was reported for certificate based handshakes, but may also affect PSK based handshakes. It generally affects client and server as well. This issue is patched in version 3.7.0 and 2.7.4. There are no known workarounds. main: commit 726bac57659410da463dcf404b3e79a7312ac0b9 2.7.x: commit 5648a0c27c2c2667c98419254557a14bac2b1f3f

CVSS v3 base score: 8.2, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H)

Weakness information:
cwe_id: CWE-404
cwe_name: Improper Resource Shutdown or Release
description: The product does not release or incorrectly releases a resource before it is made available for re-use.
extended_description: When a resource is created or allocated, the developer is responsible for properly releasing the resource as well as accounting for all potential paths of expiration or invalidation, such as a set period of time or revocation.
         
cwe_id: CWE-459
cwe_name: Incomplete Cleanup
description: The product does not properly "clean up" and remove temporary or supporting resources after they have been used.
extended_description: 
            
            
         
         

Fix information:
repo_url: https://github.com/eclipse-californium/californium
commit_details: repo_url: https://github.com/eclipse-californium/californium
author: Achim Kraus
author_date: 2022-09-15 18:22:12+02:00
msg: Fix pending job counters.

Run pending jobs on connection shutdown to align the counters.

Fixes issue #2065

Signed-off-by: Achim Kraus <achim.kraus@cloudcoap.net>
repository: repo_url: https://github.com/eclipse-californium/californium
repo_name: eclipse-californium/californium
description: CoAP/DTLS Java Implementation 
repo_language: Java
owner: eclipse-californium
file_changes: filename: ExecutorsUtil.java
change_type: ModificationType.MODIFY
diff: @@ -234,6 +234,25 @@ public class ExecutorsUtil {
 		}
 	}
 
+	/**
+	 * Run all jobs.
+	 * 
+	 * On {@link Throwable} write only warning and continue to run the other
+	 * jobs.
+	 * 
+	 * @param jobs list of jobs to run.
+	 * @since 3.7
+	 */
+	public static void runAll(List<Runnable> jobs) {
+		for (Runnable job : jobs) {
+			try {
+				job.run();
+			} catch (Throwable e) {
+				LOGGER.warn("Ignoring error:", e);
+			}
+		}
+	}
+
 	/**
 	 * Set remove on cancel policy of provided executor.
 	 * 

programming_language: Java
filename: LimitedRunnable.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v20.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ ******************************************************************************/
+package org.eclipse.californium.elements.util;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Limited jobs.
+ *
+ * Limits pending jobs based on counters.
+ * 
+ * Note: for proper operation, ensure the at the end of
+ * {@link Runnable#run()} the {@link LimitedRunnable#onDequeueing()} is
+ * called.
+ * 
+ * <pre>
+ *	public void run() {
+ *		try {
+ *			if (running.get() &amp;&amp; connection.isExecuting()) {
+ *				// process
+ *			}
+ *		} finally {
+ *			onDequeueing();
+ *		}
+ *	}
+ * </pre>
+ * 
+ * @since 3.7
+ */
+public abstract class LimitedRunnable implements Runnable {
+
+	/**
+	 * Down counter to limit pending jobs.
+	 */
+	private final AtomicInteger counter;
+	/**
+	 * Indicator for overflows. {@code true}, if the counter indicates an
+	 * overflow, {@code false}, otherwise.
+	 */
+	private volatile boolean overflow;
+
+	/**
+	 * Create job limited by the provided counter
+	 * 
+	 * @param counter counter in count-down mode
+	 */
+	public LimitedRunnable(AtomicInteger counter) {
+		this.counter = counter;
+	}
+
+	/**
+	 * Queue job.
+	 * 
+	 * @throws RejectedExecutionException if limit is exceeded
+	 */
+	public void onQueueing() {
+		if (counter.decrementAndGet() < 0) {
+			overflow = true;
+			throw new RejectedExecutionException("queue overflow!");
+		}
+	}
+
+	/**
+	 * Dequeue job.
+	 */
+	public void onDequeueing() {
+		counter.incrementAndGet();
+	}
+
+	/**
+	 * Checks, if queueing this job causes an counter overflow.
+	 * 
+	 * @return {@code true}, if the counter indicates an overflow,
+	 *         {@code false}, otherwise.
+	 */
+	public boolean isOverflown() {
+		return overflow;
+	}
+
+	/**
+	 * Execute this job.
+	 * 
+	 * @param executor executor to execute jobs.
+	 */
+	public void execute(Executor executor) {
+		try {
+			onQueueing();
+			executor.execute(this);
+		} catch (RejectedExecutionException ex) {
+			onDequeueing();
+			throw ex;
+		}
+	}
+}
\ No newline at end of file

programming_language: Java
filename: SerialExecutor.java
change_type: ModificationType.MODIFY
diff: @@ -90,10 +90,14 @@ public class SerialExecutor extends AbstractExecutorService {
 	 * 
 	 * @param executor target executor. If {@code null}, the executor is
 	 *            shutdown.
+	 * @throws IllegalArgumentException if the executor is also a
+	 *             {@link SerialExecutor}
 	 */
 	public SerialExecutor(final Executor executor) {
 		if (executor == null) {
 			shutdown = true;
+		} else if (executor instanceof SerialExecutor) {
+			throw new IllegalArgumentException("Sequences of SerialExecutors are not supported!");
 		}
 		this.executor = executor;
 	}
@@ -211,7 +215,7 @@ public class SerialExecutor extends AbstractExecutorService {
 	}
 
 	/**
-	 * Shutdown this executor and add all pending task from {@link #tasks} to
+	 * Shutdown this executor and add all pending jobs from {@link #tasks} to
 	 * the provided collection.
 	 * 
 	 * @param jobs collection to add pending jobs.
@@ -280,31 +284,31 @@ public class SerialExecutor extends AbstractExecutorService {
 					@Override
 					public void run() {
 						try {
+							setOwner();
+							ExecutionListener current = listener.get();
 							try {
-								setOwner();
-								ExecutionListener current = listener.get();
+								if (current != null) {
+									current.beforeExecution();
+								}
+								command.run();
+							} catch (Throwable t) {
+								LOGGER.error("unexpected error occurred:", t);
+							} finally {
 								try {
 									if (current != null) {
-										current.beforeExecution();
+										current.afterExecution();
 									}
-									command.run();
 								} catch (Throwable t) {
-									LOGGER.error("unexpected error occurred:", t);
-								} finally {
-									try {
-										if (current != null) {
-											current.afterExecution();
-										}
-									} catch (Throwable t) {
-										LOGGER.error("unexpected error occurred:", t);
-									}
-									clearOwner();
+									LOGGER.error("unexpected error occurred after execution:", t);
 								}
-							} finally {
+								clearOwner();
+							}
+						} finally {
+							try {
 								scheduleNextJob();
+							} catch (RejectedExecutionException ex) {
+								LOGGER.debug("shutdown?", ex);
 							}
-						} catch (RejectedExecutionException ex) {
-							LOGGER.debug("shutdown?", ex);
 						}
 					}
 				});
@@ -332,7 +336,8 @@ public class SerialExecutor extends AbstractExecutorService {
 	/**
 	 * Execution listener.
 	 * 
-	 * Called before and after executing a task.
+	 * Called before and after executing a task. The calling thread is the same
+	 * as the the one executing the job.
 	 * 
 	 * @since 2.4
 	 */

programming_language: Java
filename: api-changes.json
change_type: ModificationType.MODIFY
diff: @@ -400,5 +400,30 @@
 				]
 			}
 		}
+	],
+	"3.7.0": [
+		{
+			"extension": "revapi.differences",
+			"configuration": {
+				"ignore": true,
+				"differences": [
+					{
+						"code": "java.method.removed",
+						"old": "method boolean org.eclipse.californium.scandium.DTLSConnector::executeHandshakeResult(java.util.concurrent.Executor, org.eclipse.californium.scandium.dtls.Connection, java.lang.Runnable)",
+						"justification": "Not part of the public API"
+					},
+					{
+						"code": "java.method.removed",
+						"old": "method boolean org.eclipse.californium.scandium.DTLSConnector::executeInbound(java.util.concurrent.Executor, java.net.InetSocketAddress, java.lang.Runnable)",
+						"justification": "Not part of the public API"
+					},
+					{
+						"code": "java.method.removed",
+						"old": "method boolean org.eclipse.californium.scandium.DTLSConnector::executeOutbound(java.util.concurrent.Executor, java.net.InetSocketAddress, java.lang.Runnable)",
+						"justification": "Not part of the public API"
+					}
+				]
+			}
+		}
 	]
 }

programming_language: Java
filename: DTLSConnector.java
change_type: ModificationType.MODIFY
diff: @@ -180,6 +180,7 @@ import org.eclipse.californium.elements.util.DatagramReader;
 import org.eclipse.californium.elements.util.ExecutorsUtil;
 import org.eclipse.californium.elements.util.FilteredLogger;
 import org.eclipse.californium.elements.util.LeastRecentlyUsedCache;
+import org.eclipse.californium.elements.util.LimitedRunnable;
 import org.eclipse.californium.elements.util.NamedThreadFactory;
 import org.eclipse.californium.elements.util.NetworkInterfacesUtil;
 import org.eclipse.californium.elements.util.NoPublicAPI;
@@ -770,6 +771,46 @@ public class DTLSConnector implements Connector, PersistentConnector, Persistent
 		return health;
 	}
 
+	/**
+	 * Update health statistic.
+	 * 
+	 * Update {@link DtlsHealthExtended#setConnections(int)},
+	 * {@link DtlsHealthExtended2#setPendingIncomingJobs(int)},
+	 * {@link DtlsHealthExtended2#setPendingOutgoingJobs(int)}, and
+	 * {@link DtlsHealthExtended2#setPendingHandshakeJobs(int)}.
+	 * 
+	 * @return {@code true}, if some pending jobs left, {@code false}, if not.
+	 * @since 3.7
+	 */
+	public boolean updateHealth() {
+		boolean pending = false;
+		if (health instanceof DtlsHealthExtended) {
+			((DtlsHealthExtended) health).setConnections(maxConnections - connectionStore.remainingCapacity());
+		}
+		if (health instanceof DtlsHealthExtended2) {
+			DtlsHealthExtended2 health2 = (DtlsHealthExtended2) health;
+			int jobs = maxPendingOutboundJobs - pendingOutboundJobsCountdown.get();
+			health2.setPendingOutgoingJobs(jobs);
+			if (jobs > 0) {
+				LOGGER.debug("Pending out jobs {}", jobs);
+			}
+			pending = jobs > 0;
+			jobs = maxPendingInboundJobs - pendingInboundJobsCountdown.get();
+			health2.setPendingIncomingJobs(jobs);
+			if (jobs > 0) {
+				LOGGER.debug("Pending in jobs {}", jobs);
+			}
+			pending |= jobs > 0;
+			jobs = maxPendingHandshakeResultJobs - pendingHandshakeResultJobsCountdown.get();
+			health2.setPendingHandshakeJobs(jobs);
+			if (jobs > 0) {
+				LOGGER.debug("Pending handshake jobs {}", jobs);
+			}
+			pending |= jobs > 0;
+		}
+		return pending;
+	}
+
 	/**
 	 * Create default health handler.
 	 * 
@@ -1277,14 +1318,14 @@ public class DTLSConnector implements Connector, PersistentConnector, Persistent
 							health.dump(config.getLoggingTag(), maxConnections,
 									connectionStore.remainingCapacity(), pendingHandshakesWithoutVerifiedPeer.get());
 							lastNanos = now;
-						} else if (health instanceof DtlsHealthExtended) {
-							((DtlsHealthExtended) health)
-									.setConnections(maxConnections - connectionStore.remainingCapacity());
+						} else {
+							updateHealth();
 						}
 					}
 
 				}, intervalMillis, intervalMillis, TimeUnit.MILLISECONDS);
 			}
+			updateHealth();
 		}
 
 		recentHandshakeCleaner = timer.scheduleWithFixedDelay(new Runnable() {
@@ -1422,13 +1463,7 @@ public class DTLSConnector implements Connector, PersistentConnector, Persistent
 			} catch (InterruptedException e) {
 			}
 		}
-		for (Runnable job : pending) {
-			try {
-				job.run();
-			} catch (Exception e) {
-				LOGGER.warn("Shutdown DTLS connector:", e);
-			}
-		}
+		ExecutorsUtil.runAll(pending);
 		if (stop) {
 			LOGGER.debug("DTLS connector on [{}] stopped.", lastBindAddress);
 		}
@@ -1842,18 +1877,23 @@ public class DTLSConnector implements Connector, PersistentConnector, Persistent
 				}
 				return;
 			}
-			executeInbound(getExecutorService(), peerAddress, new Runnable() {
+			executeInbound(getExecutorService(), peerAddress, new LimitedRunnable(pendingInboundJobsCountdown) {
 
 				@Override
 				public void run() {
-					if (MDC_SUPPORT) {
-						MDC.put("PEER", StringUtil.toString(firstRecord.getPeerAddress()));
-					}
-					processNewClientHello(firstRecord);
-					if (MDC_SUPPORT) {
-						MDC.clear();
+					try {
+						if (running.get()) {
+							if (MDC_SUPPORT) {
+								MDC.put("PEER", StringUtil.toString(firstRecord.getPeerAddress()));
+							}
+							processNewClientHello(firstRecord);
+							if (MDC_SUPPORT) {
+								MDC.clear();
+							}
+						}
+					} finally {
+						onDequeueing();
 					}
-					pendingInboundJobsCountdown.incrementAndGet();
 				}
 			});
 			return;
@@ -1881,16 +1921,16 @@ public class DTLSConnector implements Connector, PersistentConnector, Persistent
 		for (final Record record : records) {
 			record.setAddress(peerAddress, router);
 			try {
-				if (!executeInbound(serialExecutor, peerAddress, new Runnable() {
+				if (!executeInbound(serialExecutor, peerAddress, new LimitedRunnable(pendingInboundJobsCountdown) {
 
 					@Override
 					public void run() {
 						try {
-							if (running.get()) {
+							if (running.get() && connection.isExecuting()) {
 								processRecord(record, connection);
 							}
 						} finally {
-							pendingInboundJobsCountdown.incrementAndGet();
+							onDequeueing();
 						}
 					}
 				})) {
@@ -1915,33 +1955,24 @@ public class DTLSConnector implements Connector, PersistentConnector, Persistent
 	 *         execution was denied
 	 * @since 3.5
 	 */
-	protected boolean executeInbound(Executor executor, InetSocketAddress peer, Runnable job) {
-		boolean pending = false;
+	@NoPublicAPI
+	protected boolean executeInbound(Executor executor, InetSocketAddress peer, LimitedRunnable job) {
 		try {
-			int count = pendingInboundJobsCountdown.decrementAndGet();
-			if (count >= 0) {
-				executor.execute(job);
-				pending = true;
-				if (health instanceof DtlsHealthExtended2) {
-					((DtlsHealthExtended2) health).setPendingIncomingJobs(maxPendingInboundJobs - count);
-				}
-			} else {
+			job.execute(executor);
+			return true;
+		} catch (RejectedExecutionException e) {
+			if (job.isOverflown()) {
 				DROP_LOGGER_IN_FILTERED.info("Inbound jobs overflow! Dropping inbound message from peer [{}]",
 						StringUtil.toLog(peer));
-			}
-		} catch (RejectedExecutionException e) {
-			// dont't terminate connection on shutdown!
-			LOGGER.debug("Execution rejected while processing record from peer [{}]",
-					StringUtil.toLog(peer), e);
-		} finally {
-			if (!pending) {
-				if (health != null) {
-					health.receivingRecord(true);
-				}
-				pendingInboundJobsCountdown.incrementAndGet();
+			} else {
+				// dont't terminate connection on shutdown!
+				LOGGER.debug("Execution rejected while processing record from peer [{}]", StringUtil.toLog(peer), e);
 			}
 		}
-		return pending;
+		if (health != null) {
+			health.receivingRecord(true);
+		}
+		return false;
 	}
 
 	/**
@@ -2930,47 +2961,38 @@ public class DTLSConnector implements Connector, PersistentConnector, Persistent
 		}
 
 		final long now = ClockUtil.nanoRealtime();
-		try {
-			SerialExecutor executor = connection.getExecutor();
-			if (!executeOutbound(executor, message.getInetSocketAddress(), new Runnable() {
+		SerialExecutor executor = connection.getExecutor();
+		if (!executeOutbound(executor, message.getInetSocketAddress(), new LimitedRunnable(pendingOutboundJobsCountdown) {
 
-				@Override
-				public void run() {
-					try {
-						if (running.get()) {
-							sendMessage(now, message, connection);
-						} else {
-							DROP_LOGGER.trace("DTLSConnector drops {} outgoing bytes to {}, connector not running!",
-									message.getSize(), StringUtil.toLog(message.getInetSocketAddress()));
-							message.onError(new InterruptedIOException("Connector is not running."));
-							if (health != null) {
-								health.sendingRecord(true);
-							}
-						}
-					} catch (Exception e) {
-						if (running.get()) {
-							LOGGER.warn("Exception thrown by executor thread [{}]",
-									Thread.currentThread().getName(), e);
-						}
-						DROP_LOGGER.trace("DTLSConnector drops {} outgoing bytes to {} {}", message.getSize(),
-								StringUtil.toLog(message.getInetSocketAddress()), e.getMessage());
+			@Override
+			public void run() {
+				try {
+					if (running.get() && connection.isExecuting()) {
+						sendMessage(now, message, connection);
+					} else {
+						DROP_LOGGER.trace("DTLSConnector drops {} outgoing bytes to {}, connector not running!",
+								message.getSize(), StringUtil.toLog(message.getInetSocketAddress()));
+						message.onError(new InterruptedIOException("Connector is not running."));
 						if (health != null) {
 							health.sendingRecord(true);
 						}
-						message.onError(e);
-					} finally {
-						pendingOutboundJobsCountdown.incrementAndGet();
 					}
+				} catch (Exception e) {
+					if (running.get()) {
+						LOGGER.warn("Exception thrown by executor thread [{}]", Thread.currentThread().getName(), e);
+					}
+					DROP_LOGGER.trace("DTLSConnector drops {} outgoing bytes to {} {}", message.getSize(),
+							StringUtil.toLog(message.getInetSocketAddress()), e.getMessage());
+					if (health != null) {
+						health.sendingRecord(true);
+					}
+					message.onError(e);
+				} finally {
+					onDequeueing();
 				}
-			})) {
-				message.onError(new IllegalStateException("Outbound message overflow!"));
 			}
-		} catch (RejectedExecutionException e) {
-			LOGGER.debug("Execution rejected while sending application record [peer: {}]",
-					StringUtil.toLog(message.getInetSocketAddress()), e);
-			DROP_LOGGER.trace("DTLSConnector drops {} outgoing bytes to {}, {}", message.getSize(),
-					StringUtil.toLog(message.getInetSocketAddress()), e.getMessage());
-			message.onError(new InterruptedIOException("Connector is not running."));
+		})) {
+			message.onError(new IllegalStateException("Outbound message overflow!"));
 		}
 	}
 
@@ -2984,29 +3006,24 @@ public class DTLSConnector implements Connector, PersistentConnector, Persistent
 	 *         execution was denied
 	 * @since 3.5
 	 */
-	protected boolean executeOutbound(Executor executor, InetSocketAddress peer, Runnable job) {
-		boolean pending = false;
+	@NoPublicAPI
+	protected boolean executeOutbound(Executor executor, InetSocketAddress peer, LimitedRunnable job) {
 		try {
-			int count = pendingOutboundJobsCountdown.decrementAndGet();
-			if (count >= 0) {
-				executor.execute(job);
-				pending = true;
-				if (health instanceof DtlsHealthExtended2) {
-					((DtlsHealthExtended2) health).setPendingOutgoingJobs(maxPendingOutboundJobs - count);
-				}
-			} else {
+			job.execute(executor);
+			return true;
+		} catch (RejectedExecutionException e) {
+			if (job.isOverflown()) {
 				DROP_LOGGER_OUT_FILTERED.info("Outbound jobs overflow! Dropping outbound message to peer [{}]",
 						StringUtil.toLog(peer));
-			}
-		} finally {
-			if (!pending) {
-				if (health != null) {
-					health.sendingRecord(true);
-				}
-				pendingOutboundJobsCountdown.incrementAndGet();
+			} else {
+				// dont't terminate connection on shutdown!
+				LOGGER.debug("Execution rejected while processing record to peer [{}]", StringUtil.toLog(peer), e);
 			}
 		}
-		return pending;
+		if (health != null) {
+			health.sendingRecord(true);
+		}
+		return false;
 	}
 
 	/**
@@ -3406,12 +3423,12 @@ public class DTLSConnector implements Connector, PersistentConnector, Persistent
 			if (connection.hasOngoingHandshake()) {
 				try {
 					SerialExecutor serialExecutor = connection.getExecutor();
-					executeHandshakeResult(serialExecutor, connection, new Runnable() {
+					executeHandshakeResult(serialExecutor, connection, new LimitedRunnable(pendingHandshakeResultJobsCountdown) {
 
 						@Override
 						public void run() {
 							try {
-								if (running.get()) {
+								if (running.get() && connection.isExecuting()) {
 									Handshaker handshaker = connection.getOngoingHandshake();
 									if (handshaker != null) {
 										handshaker.processAsyncHandshakeResult(handshakeResult);
@@ -3427,13 +3444,10 @@ public class DTLSConnector implements Connector, PersistentConnector, Persistent
 							} catch (IllegalStateException e) {
 								LOGGER.warn("Exception while processing handshake result [{}]", connection, e);
 							} finally {
-								pendingHandshakeResultJobsCountdown.incrementAndGet();
+								onDequeueing();
 							}
 						}
 					});
-				} catch (RejectedExecutionException e) {
-					// dont't terminate connection on shutdown!
-					LOGGER.debug("Execution rejected while processing handshake result [{}]", connection);
 				} catch (RuntimeException e) {
 					LOGGER.warn("Unexpected error occurred while processing handshake result [{}]", connection, e);
 				}
@@ -3455,26 +3469,21 @@ public class DTLSConnector implements Connector, PersistentConnector, Persistent
 	 *         execution was denied
 	 * @since 3.5
 	 */
-	protected boolean executeHandshakeResult(Executor executor, Connection connection, Runnable job) {
-		boolean pending = false;
+	@NoPublicAPI
+	protected boolean executeHandshakeResult(Executor executor, Connection connection, LimitedRunnable job) {
 		try {
-			int count = pendingHandshakeResultJobsCountdown.decrementAndGet();
-			if (count >= 0) {
-				executor.execute(job);
-				pending = true;
-				if (health instanceof DtlsHealthExtended2) {
-					((DtlsHealthExtended2) health).setPendingHandshakeJobs(maxPendingHandshakeResultJobs - count);
-				}
-			} else {
+			job.execute(executor);
+			return true;
+		} catch (RejectedExecutionException e) {
+			if (job.isOverflown()) {
 				DROP_LOGGER_HANDSHAKE_RESULTS_FILTERED
 						.info("Handshake result jobs overflow! Dropping handshake result [{}]", connection);
-			}
-		} finally {
-			if (!pending) {
-				pendingHandshakeResultJobsCountdown.incrementAndGet();
+			} else {
+				// dont't terminate connection on shutdown!
+				LOGGER.debug("Execution rejected while processing handshake result [{}]", connection, e);
 			}
 		}
-		return pending;
+		return false;
 	}
 
 	/**

programming_language: Scala
filename: DtlsBindingPreprocessingConnector.java
change_type: ModificationType.MODIFY
diff: @@ -82,7 +82,7 @@ public class DtlsBindingPreprocessingConnector extends DTLSConnector {
 	 * <p>
 	 * If created action is running on separate threads, consider limiting such pending jobs in order
 	 * to protect against DoS attack. Similar approach may be seen in
-	 * {@link DTLSConnector#executeInbound(Executor, InetSocketAddress, Runnable)}
+	 * {@link DTLSConnector#executeInbound(Executor, InetSocketAddress, org.eclipse.californium.scandium.DTLSConnector.LimitedRunnable)}
 	 * </p>
 	 */
 	public interface DtlsBindingActionWithCallback {

programming_language: Java
filename: Connection.java
change_type: ModificationType.MODIFY
diff: @@ -42,6 +42,7 @@ import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.security.Principal;
 import java.util.ConcurrentModificationException;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -54,6 +55,7 @@ import org.eclipse.californium.elements.util.ClockUtil;
 import org.eclipse.californium.elements.util.DataStreamReader;
 import org.eclipse.californium.elements.util.DatagramReader;
 import org.eclipse.californium.elements.util.DatagramWriter;
+import org.eclipse.californium.elements.util.ExecutorsUtil;
 import org.eclipse.californium.elements.util.SerialExecutor;
 import org.eclipse.californium.elements.util.SerialExecutor.ExecutionListener;
 import org.eclipse.californium.elements.util.SerializationUtil;
@@ -119,7 +121,7 @@ public final class Connection {
 	 */
 	private long lastMessageNanos;
 	private long lastPeerAddressNanos;
-	private SerialExecutor serialExecutor;
+	private volatile SerialExecutor serialExecutor;
 	private InetSocketAddress peerAddress;
 	private InetSocketAddress router;
 	private ConnectionId cid;
@@ -228,6 +230,25 @@ public final class Connection {
 		return serialExecutor != null && !serialExecutor.isShutdown();
 	}
 
+	/**
+	 * Shutdown executor and run all pending jobs.
+	 * 
+	 * The jobs are intended to check {@link #isExecuting()} in order to detect
+	 * the shutdown.
+	 * 
+	 * @return number of pending jobs.
+	 * @since 3.7
+	 */
+	public int shutdown() {
+		SerialExecutor executor = getExecutor();
+		if (executor != null) {
+			List<Runnable> pendings = executor.shutdownNow();
+			ExecutorsUtil.runAll(pendings);
+			return pendings.size();
+		}
+		return 0;
+	}
+
 	/**
 	 * Get session listener of connection.
 	 * 

programming_language: Java
filename: InMemoryConnectionStore.java
change_type: ModificationType.MODIFY
diff: @@ -504,24 +504,15 @@ public class InMemoryConnectionStore implements ResumptionSupportingConnectionSt
 		synchronized (this) {
 			removed = connections.remove(connection.getConnectionId(), connection) == connection;
 			if (removed) {
-				if (connection.isExecuting()) {
-					List<Runnable> pendings = connection.getExecutor().shutdownNow();
-					if (LOGGER.isTraceEnabled()) {
-						LOGGER.trace("{}connection: remove {} (size {}, left jobs: {})", tag, connection,
-								connections.size(), pendings.size(), new Throwable("connection removed!"));
-					} else if (pendings.isEmpty()) {
-						LOGGER.debug("{}connection: remove {} (size {})", tag, connection, connections.size());
-					} else {
-						LOGGER.debug("{}connection: remove {} (size {}, left jobs: {})", tag, connection,
-								connections.size(), pendings.size());
-					}
+				int pendings = connection.shutdown();
+				if (LOGGER.isTraceEnabled()) {
+					LOGGER.trace("{}connection: remove {} (size {}, left jobs: {})", tag, connection,
+							connections.size(), pendings, new Throwable("connection removed!"));
+				} else if (pendings == 0) {
+					LOGGER.debug("{}connection: remove {} (size {})", tag, connection, connections.size());
 				} else {
-					if (LOGGER.isTraceEnabled()) {
-						LOGGER.trace("{}connection: remove {} (size {})", tag, connection, connections.size(),
-								new Throwable("connection removed!"));
-					} else {
-						LOGGER.debug("{}connection: remove {} (size {})", tag, connection, connections.size());
-					}
+					LOGGER.debug("{}connection: remove {} (size {}, left jobs: {})", tag, connection,
+							connections.size(), pendings);
 				}
 				connection.startByClientHello(null);
 				removeByAddressConnections(connection);

programming_language: Java
filename: InMemoryReadWriteLockConnectionStore.java
change_type: ModificationType.MODIFY
diff: @@ -582,24 +582,15 @@ public class InMemoryReadWriteLockConnectionStore implements ReadWriteLockConnec
 		try {
 			removed = connections.remove(connection.getConnectionId(), connection) == connection;
 			if (removed) {
-				if (connection.isExecuting()) {
-					List<Runnable> pendings = connection.getExecutor().shutdownNow();
-					if (LOGGER.isTraceEnabled()) {
-						LOGGER.trace("{}connection: remove {} (size {}, left jobs: {})", tag, connection,
-								connections.size(), pendings.size(), new Throwable("connection removed!"));
-					} else if (pendings.isEmpty()) {
-						LOGGER.debug("{}connection: remove {} (size {})", tag, connection, connections.size());
-					} else {
-						LOGGER.debug("{}connection: remove {} (size {}, left jobs: {})", tag, connection,
-								connections.size(), pendings.size());
-					}
+				int pendings = connection.shutdown();
+				if (LOGGER.isTraceEnabled()) {
+					LOGGER.trace("{}connection: remove {} (size {}, left jobs: {})", tag, connection,
+							connections.size(), pendings, new Throwable("connection removed!"));
+				} else if (pendings == 0) {
+					LOGGER.debug("{}connection: remove {} (size {})", tag, connection, connections.size());
 				} else {
-					if (LOGGER.isTraceEnabled()) {
-						LOGGER.trace("{}connection: remove {} (size {})", tag, connection, connections.size(),
-								new Throwable("connection removed!"));
-					} else {
-						LOGGER.debug("{}connection: remove {} (size {})", tag, connection, connections.size());
-					}
+					LOGGER.debug("{}connection: remove {} (size {}, left jobs: {})", tag, connection,
+							connections.size(), pendings);
 				}
 				connection.startByClientHello(null);
 				removeByAddressConnections(connection);

programming_language: Java
filename: DTLSConnectorAdvancedTest.java
change_type: ModificationType.MODIFY
diff: @@ -3213,7 +3213,7 @@ public class DTLSConnectorAdvancedTest {
 	}
 
 	@Test
-	public void testDisableHelloVerifRequestForPsk() throws Exception {
+	public void testDisableHelloVerifyRequestForPsk() throws Exception {
 		alternativeServerHelper = new ConnectorHelper(network);
 
 		alternativeServerHelper.serverBuilder
@@ -3274,7 +3274,7 @@ public class DTLSConnectorAdvancedTest {
 	}
 
 	@Test
-	public void testDisabledHelloVerifRequestForPskWithCertificate() throws Exception {
+	public void testDisabledHelloVerifyRequestForPskWithCertificate() throws Exception {
 		alternativeServerHelper = new ConnectorHelper(network);
 
 		alternativeServerHelper.serverBuilder
@@ -3336,7 +3336,7 @@ public class DTLSConnectorAdvancedTest {
 	}
 
 	@Test
-	public void testDisabledHelloVerifRequestWithCertificate() throws Exception {
+	public void testDisabledHelloVerifyRequestWithCertificate() throws Exception {
 		alternativeServerHelper = new ConnectorHelper(network);
 
 		alternativeServerHelper.serverBuilder
@@ -3754,7 +3754,7 @@ public class DTLSConnectorAdvancedTest {
 			cid = new ConnectionId(cidBytes);
 		}
 		Connection connection = new Connection(peer);
-		connection.setConnectorContext(new SerialExecutor(executor), null);
+		connection.setConnectorContext(executor, null);
 		connection.setConnectionId(cid);
 		return connection;
 	}

programming_language: Java
filename: DTLSConnectorHandshakeTest.java
change_type: ModificationType.MODIFY
diff: @@ -76,6 +76,8 @@ import org.eclipse.californium.elements.util.SimpleMessageCallback;
 import org.eclipse.californium.elements.util.SslContextUtil;
 import org.eclipse.californium.elements.util.SslContextUtil.Credentials;
 import org.eclipse.californium.elements.util.TestCertificatesTools;
+import org.eclipse.californium.elements.util.TestCondition;
+import org.eclipse.californium.elements.util.TestConditionTools;
 import org.eclipse.californium.elements.util.TestScope;
 import org.eclipse.californium.scandium.ConnectorHelper.AlertCatcher;
 import org.eclipse.californium.scandium.ConnectorHelper.BuilderSetup;
@@ -163,6 +165,8 @@ public class DTLSConnectorHandshakeTest {
 	AsyncAdvancedPskStore serverPskStore;
 	AsyncNewAdvancedCertificateVerifier serverVerifier;
 
+	DtlsHealthLogger serverHealth;
+
 	DtlsConnectorConfig.Builder clientBuilder;
 	DTLSConnector client;
 	TestContext clientTestContext;
@@ -379,6 +383,23 @@ public class DTLSConnectorHandshakeTest {
 	 */
 	@After
 	public void cleanUp() {
+		if (serverHelper != null && serverHelper.server != null) {
+			assertThat(serverHelper.server.isRunning(), is(true));
+			try {
+				// wait until no pending jobs left
+				TestConditionTools.waitForCondition(6000, 100, TimeUnit.MILLISECONDS, new TestCondition() {
+
+					@Override
+					public boolean isFulFilled() throws IllegalStateException {
+						return !serverHelper.server.updateHealth();
+					}
+				});
+				TestConditionTools.assertStatisticCounter("jobs left", serverHealth, "pending in jobs", is(0L));
+				TestConditionTools.assertStatisticCounter("jobs left", serverHealth, "pending out jobs", is(0L));
+				TestConditionTools.assertStatisticCounter("jobs left", serverHealth, "pending handshake jobs", is(0L));
+			} catch (InterruptedException e) {
+			}
+		}
 		for (AsyncAdvancedPskStore pskStore : clientsPskStores) {
 			pskStore.shutdown();
 		}
@@ -443,7 +464,8 @@ public class DTLSConnectorHandshakeTest {
 				serverVerifier.setDelay(DtlsTestTools.DEFAULT_HANDSHAKE_RESULT_DELAY_MILLIS);
 			}
 		}
-
+		serverHealth = new DtlsHealthLogger("server");
+		serverBuilder.setHealthHandler(serverHealth);
 		serverBuilderSetup.setup(serverBuilder);
 		serverHelper.startServer();
 	}

programming_language: Java
filename: DTLSConnectorTest.java
change_type: ModificationType.MODIFY
diff: @@ -83,7 +83,6 @@ import org.eclipse.californium.elements.rule.ThreadsRule;
 import org.eclipse.californium.elements.util.DatagramReader;
 import org.eclipse.californium.elements.util.DatagramWriter;
 import org.eclipse.californium.elements.util.ExecutorsUtil;
-import org.eclipse.californium.elements.util.SerialExecutor;
 import org.eclipse.californium.elements.util.SimpleMessageCallback;
 import org.eclipse.californium.elements.util.TestThreadFactory;
 import org.eclipse.californium.scandium.ConnectorHelper.LatchDecrementingRawDataChannel;
@@ -821,7 +820,7 @@ public class DTLSConnectorTest {
 		DatagramReader reader = new DatagramReader(writer.toByteArray());
 		Connection connection2 = Connection.fromReader(reader, 0);
 		clientConnectionStore.remove(connection, true);
-		connection2.setConnectorContext(new SerialExecutor(executor), null);
+		connection2.setConnectorContext(executor, null);
 		clientConnectionStore.put(connection2);
 
 		// Restart it
@@ -1035,11 +1034,11 @@ public class DTLSConnectorTest {
 		serverHelper.serverConnectionStore.clear();
 		assertEquals(SERVER_CONNECTION_STORE_CAPACITY, serverHelper.serverConnectionStore.remainingCapacity());
 		assertTrue(serverHelper.serverConnectionStore.put(new Connection(new InetSocketAddress("192.168.0.1", 5050))
-				.setConnectorContext(new SerialExecutor(executor), null)));
+				.setConnectorContext(executor, null)));
 		assertTrue(serverHelper.serverConnectionStore.put(new Connection(new InetSocketAddress("192.168.0.2", 5050))
-				.setConnectorContext(new SerialExecutor(executor), null)));
+				.setConnectorContext(executor, null)));
 		assertTrue(serverHelper.serverConnectionStore.put(new Connection(new InetSocketAddress("192.168.0.3", 5050))
-				.setConnectorContext(new SerialExecutor(executor), null)));
+				.setConnectorContext(executor, null)));
 
 		LatchDecrementingRawDataChannel clientRawDataChannel = new LatchDecrementingRawDataChannel(1);
 		client.setRawDataReceiver(clientRawDataChannel);

programming_language: Java