From d8672d9221a3921766f05716ce5c9fe8f567e65a Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Fri, 20 Feb 2026 17:36:22 -0800 Subject: [PATCH] Skip returned null KnownHosts entries The called Extended Host Verifier can return a list entry with null in it which can cause issues when asking the implementation to remove it from its database. Skip those entries instead of sending them. --- .../trilead/ssh2/channel/ChannelManager.java | 2 ++ .../ssh2/channel/ChannelManagerTest.java | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/main/java/com/trilead/ssh2/channel/ChannelManager.java b/src/main/java/com/trilead/ssh2/channel/ChannelManager.java index 7354bf55..e097b6fa 100644 --- a/src/main/java/com/trilead/ssh2/channel/ChannelManager.java +++ b/src/main/java/com/trilead/ssh2/channel/ChannelManager.java @@ -1901,6 +1901,8 @@ private void processHostkeysAdvertisement(PacketGlobalHostkeys hostkeys, String if (knownAlgos != null) { for (String knownAlgo : knownAlgos) { + if (knownAlgo == null) + continue; if (!advertisedAlgoSet.contains(knownAlgo)) { extVerifier.removeServerHostKey(hostname, port, knownAlgo, null); if (log.isEnabled()) diff --git a/src/test/java/com/trilead/ssh2/channel/ChannelManagerTest.java b/src/test/java/com/trilead/ssh2/channel/ChannelManagerTest.java index 0c8c82b6..3452d3e9 100644 --- a/src/test/java/com/trilead/ssh2/channel/ChannelManagerTest.java +++ b/src/test/java/com/trilead/ssh2/channel/ChannelManagerTest.java @@ -1,7 +1,10 @@ package com.trilead.ssh2.channel; import com.trilead.ssh2.ChannelCondition; +import com.trilead.ssh2.ExtendedServerHostKeyVerifier; +import com.trilead.ssh2.packets.PacketGlobalHostkeys; import com.trilead.ssh2.packets.Packets; +import com.trilead.ssh2.packets.TypesWriter; import com.trilead.ssh2.transport.ITransportConnection; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -10,6 +13,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; +import java.util.Arrays; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -23,6 +27,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * Test coverage for ChannelManager. @@ -714,6 +719,33 @@ public void testMsgChannelOpenWithX11() throws IOException { } } + /** + * Regression test: if getKnownKeyAlgorithmsForHost returns a list with null entries, + * processHostkeysAdvertisement must not NPE when calling removeServerHostKey. + * This reproduces the crash reported via Android Play Console at source line 1905. + */ + @Test + public void testHostkeysAdvertisementWithNullKnownAlgorithmDoesNotThrow() throws IOException { + ExtendedServerHostKeyVerifier extVerifier = mock(ExtendedServerHostKeyVerifier.class); + when(mockTransportConnection.getServerHostKeyVerifier()).thenReturn(extVerifier); + when(mockTransportConnection.getHostname()).thenReturn("example.com"); + when(mockTransportConnection.getPort()).thenReturn(22); + // Return a list that contains a null entry alongside a real algorithm + when(extVerifier.getKnownKeyAlgorithmsForHost("example.com", 22)) + .thenReturn(Arrays.asList("ssh-rsa", null)); + // Build a hostkeys-00@openssh.com global request with no advertised keys + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); + tw.writeString(PacketGlobalHostkeys.HOSTKEYS_VENDOR); + tw.writeBoolean(false); + byte[] msg = tw.getBytes(); + + channelManager.msgGlobalRequest(msg, msg.length); + + // The null entry must not be passed to removeServerHostKey + verify(extVerifier, never()).removeServerHostKey(any(), any(int.class), (String) org.mockito.ArgumentMatchers.isNull(), any()); + } + @Test public void testMsgChannelOpenWithUnknownType() throws IOException { byte[] msg = new byte[100];