Medium severity5.3NVD Advisory· Published Feb 20, 2025· Updated Apr 15, 2026
CVE-2025-23020
CVE-2025-23020
Description
An issue was discovered in Kwik before 0.10.1. A hash collision vulnerability (in the hash table used to manage connections) allows remote attackers to cause a considerable CPU load on the server (a Hash DoS attack) by initiating connections with colliding Source Connection IDs (SCIDs).
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
tech.kwik:kwikMaven | < 0.10.1 | 0.10.1 |
Patches
2d1288760c437https://github.com/ptrd/kwikvia osv
b0733d72bad7use secure hash for ConnectionSource, the key in the connection registry hashtable
7 files changed · +148 −11
buildSrc/src/main/groovy/buildlogic.java-common-conventions.gradle+3 −0 modified@@ -22,6 +22,9 @@ javadoc { } repositories { + flatDir { + dirs "$rootDir/localdeps" + } mavenCentral() }
core/build.gradle+2 −1 modified@@ -2,12 +2,13 @@ plugins { id 'buildlogic.java-common-conventions' } - dependencies { implementation group: 'tech.kwik', name: 'agent15', version: "$agent15_version" // https://mvnrepository.com/artifact/at.favre.lib/hkdf implementation group: 'at.favre.lib', name: 'hkdf', version: '2.0.0' + + implementation group: 'io.whitfin', name: 'siphash', version: '2.0.0-auto-module' } task includeVersion {
core/src/main/java/module-info.java+1 −0 modified@@ -1,6 +1,7 @@ module tech.kwik.core { requires tech.kwik.agent15; requires at.favre.lib.hkdf; + requires io.whitfin.siphash; exports tech.kwik.core; exports tech.kwik.core.concurrent;
core/src/main/java/tech/kwik/core/server/impl/ConnectionSource.java+5 −2 modified@@ -19,20 +19,23 @@ package tech.kwik.core.server.impl; import tech.kwik.core.util.Bytes; +import tech.kwik.core.util.SecureHash; import java.util.Arrays; public class ConnectionSource { private final byte[] dcid; + private final int hashCode; - public ConnectionSource(byte[] dcid) { + public ConnectionSource(byte[] dcid, SecureHash secureHash) { this.dcid = dcid; + hashCode = secureHash.generateHashCode(dcid); } @Override public int hashCode() { - return Arrays.hashCode(dcid); + return hashCode; } @Override
core/src/main/java/tech/kwik/core/server/impl/ServerConnectionRegistryImpl.java+19 −8 modified@@ -21,8 +21,10 @@ import tech.kwik.core.log.Logger; import tech.kwik.core.server.ServerConnectionRegistry; import tech.kwik.core.util.Bytes; +import tech.kwik.core.util.SecureHash; import java.net.InetSocketAddress; +import java.security.SecureRandom; import java.time.Duration; import java.util.Comparator; import java.util.List; @@ -43,31 +45,40 @@ public class ServerConnectionRegistryImpl implements ServerConnectionRegistry { private final Map<ConnectionSource, ServerConnectionProxy> currentConnections; private final Lock checkEmptyLock; private final Condition allConnectionsClosed; + private final SecureHash secureHash; ServerConnectionRegistryImpl(Logger log) { this.log = log; currentConnections = new ConcurrentHashMap<>(); checkEmptyLock = new ReentrantLock(); allConnectionsClosed = checkEmptyLock.newCondition(); + + byte[] seed = new byte[16]; + new SecureRandom().nextBytes(seed); + secureHash = new SecureHash(seed); } @Override public void registerConnection(ServerConnectionProxy connection, byte[] connectionId) { - currentConnections.put(new ConnectionSource(connectionId), connection); + currentConnections.put(connectionSource(connectionId), connection); + } + + private ConnectionSource connectionSource(byte[] connectionId) { + return new ConnectionSource(connectionId, secureHash); } @Override public void deregisterConnection(ServerConnectionProxy connection, byte[] connectionId) { - currentConnections.remove(new ConnectionSource(connectionId)); + currentConnections.remove(connectionSource(connectionId)); checkAllConnectionsClosed(); } @Override public void registerAdditionalConnectionId(byte[] currentConnectionId, byte[] newConnectionId) { - ServerConnectionProxy connection = currentConnections.get(new ConnectionSource(currentConnectionId)); + ServerConnectionProxy connection = currentConnections.get(connectionSource(currentConnectionId)); if (connection != null) { - currentConnections.put(new ConnectionSource(newConnectionId), connection); + currentConnections.put(connectionSource(newConnectionId), connection); } else { log.error("Cannot add additional cid to non-existing connection " + Bytes.bytesToHex(currentConnectionId)); @@ -76,21 +87,21 @@ public void registerAdditionalConnectionId(byte[] currentConnectionId, byte[] ne @Override public void deregisterConnectionId(byte[] connectionId) { - currentConnections.remove(new ConnectionSource(connectionId)); + currentConnections.remove(connectionSource(connectionId)); checkAllConnectionsClosed(); } Optional<ServerConnectionProxy> isExistingConnection(InetSocketAddress clientAddress, byte[] dcid) { - return Optional.ofNullable(currentConnections.get(new ConnectionSource(dcid))); + return Optional.ofNullable(currentConnections.get(connectionSource(dcid))); } ServerConnectionProxy removeConnection(ServerConnectionImpl connection) { // Remove the entry this is registered with the original dcid - ServerConnectionProxy removed = currentConnections.remove(new ConnectionSource(connection.getOriginalDestinationConnectionId())); + ServerConnectionProxy removed = currentConnections.remove(connectionSource(connection.getOriginalDestinationConnectionId())); // Remove all entries that are registered with the active cids List<ServerConnectionProxy> removedConnections = connection.getActiveConnectionIds().stream() - .map(cid -> new ConnectionSource(cid)) + .map(cid -> connectionSource(cid)) .map(cs -> currentConnections.remove(cs)) .filter(Objects::nonNull) .collect(Collectors.toList());
core/src/main/java/tech/kwik/core/util/SecureHash.java+36 −0 added@@ -0,0 +1,36 @@ +/* + * Copyright © 2025 Peter Doornbosch + * + * This file is part of Kwik, an implementation of the QUIC protocol in Java. + * + * Kwik is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * Kwik is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package tech.kwik.core.util; + +import io.whitfin.siphash.SipHasher; +import io.whitfin.siphash.SipHasherContainer; + +public class SecureHash { + + private final SipHasherContainer container; + + public SecureHash(byte[] key) { + container = SipHasher.container(key); + } + + public int generateHashCode(byte[] dcid) { + long longHash = container.hash(dcid); + return (int)(longHash ^ (longHash >>> 32)); + } +}
core/src/test/java/tech/kwik/core/util/SecureHashTest.java+82 −0 added@@ -0,0 +1,82 @@ +/* + * Copyright © 2025 Peter Doornbosch + * + * This file is part of Kwik, an implementation of the QUIC protocol in Java. + * + * Kwik is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * Kwik is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package tech.kwik.core.util; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tech.kwik.core.test.ByteUtils; + +import java.security.SecureRandom; + +import static org.assertj.core.api.Assertions.assertThat; + + +class SecureHashTest { + + private SecureHash secureHash; + + @BeforeEach + void setUp() { + byte[] key = "1234567890123456".getBytes(); + secureHash = new SecureHash(key); + } + + @Test + void generatingHashForSameInputShouldReturnSameHash() { + // Given + byte[] input = ByteUtils.hexToBytes("cd8330cac6107e88"); + + // When + int hash1 = secureHash.generateHashCode(input); + int hash2 = secureHash.generateHashCode(input); + + // Then + assertThat(hash1).isEqualTo(hash2); + } + + @Test + void generatingHashForDifferentInputsShouldReturnDifferentHashes() { + // Given + byte[] input1 = ByteUtils.hexToBytes("cd8330cac6107e88"); + byte[] input2 = ByteUtils.hexToBytes("07e89cd8330cac61"); + // When + int hash1 = secureHash.generateHashCode(input1); + int hash2 = secureHash.generateHashCode(input2); + + // Then + assertThat(hash1).isNotEqualTo(hash2); + } + + @Test + void generatingHashForSameInputButDifferentSeedsShouldReturnDifferentHash() { + // Given + byte[] key = new byte[16]; + new SecureRandom().nextBytes(key); + SecureHash secureHash2 = new SecureHash(key); + + byte[] input = ByteUtils.hexToBytes("cd8330cac6107e88"); + + // When + int hash1 = secureHash.generateHashCode(input); + int hash2 = secureHash2.generateHashCode(input); + + // Then + assertThat(hash1).isNotEqualTo(hash2); + } +} \ No newline at end of file
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.