gujjwal00 / avnc

VNC Client for Android
GNU General Public License v3.0
674 stars 59 forks source link

SSH Tunnel is broken in v2.3.0: "There was a problem while connecting to xxx.xxx.xxx.xxx" #207

Closed gujjwal00 closed 9 months ago

gujjwal00 commented 9 months ago

This is caused by the 'full mode' of R8, which breaks sshlib. This mode was off by default, but AGP 8.2 seems to have tuned it on. The issue is only visible in optimized builds.

Thanks to Hiroshi MATSUOKA for reporting this.

gujjwal00 commented 9 months ago

Fixed in https://github.com/gujjwal00/avnc/commit/9a66581c92f6f87d9353596328e2047bf2820caf v2.3.1 is now available.

PureIncompetence commented 9 months ago

Unfortunately SSH-tunneling is still broken for me on v2.3.1 (F-Droid Build). I've tried with a fresh reinstall. Direct connections via :5900 are working, ssh-tunneled ones worked flawlessly up to (incl.) v2.2.3. On v2.3.1 (as on v2.3.0) they reproducably produce the following error message: ( There was a problem while connecting to 192.168.XXX.XX:22 )

On the target system sshd (OpenSSH_8.9p1) logs the following at connection attempt: sshd[2006904]: Connection closed by 192.168.XXX.XX port 49168 [preauth]

avnc logs at connection attempt:

--------- beginning of main
25632 25667 E ReceiverCoroutine: Connection failed
25632 25667 E ReceiverCoroutine: java.io.IOException: There was a problem while connecting to 192.168.XXX.XX:22
25632 25667 E ReceiverCoroutine:    at com.trilead.ssh2.Connection.connect(Connection.java:154)
25632 25667 E ReceiverCoroutine:    at com.gaurav.avnc.viewmodel.VncViewModel.access$connect(VncViewModel.kt:56)
25632 25667 E ReceiverCoroutine:    at com.gaurav.avnc.viewmodel.VncViewModel$launchConnection$1.invokeSuspend(VncViewModel.kt:41)
25632 25667 E ReceiverCoroutine:    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:9)
25632 25667 E ReceiverCoroutine:    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:111)
25632 25667 E ReceiverCoroutine:    at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:4)
25632 25667 E ReceiverCoroutine:    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:3)
25632 25667 E ReceiverCoroutine:    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:94)
25632 25667 E ReceiverCoroutine: Caused by: java.io.IOException: Key exchange was not finished, connection is closed.
25632 25667 E ReceiverCoroutine:    at com.trilead.ssh2.transport.TransportManager.getConnectionInfo(TransportManager.java:39)
25632 25667 E ReceiverCoroutine:    at com.trilead.ssh2.Connection.connect(Connection.java:79)
25632 25667 E ReceiverCoroutine:    ... 7 more
25632 25667 E ReceiverCoroutine: Caused by: java.io.IOException: java.security.InvalidKeyException: Banned public key: 0000000000000000000000000000000000000000000000000000000000000000
25632 25667 E ReceiverCoroutine:    at com.trilead.ssh2.crypto.dh.Curve25519Exchange.init(Curve25519Exchange.java:85)
25632 25667 E ReceiverCoroutine:    at com.trilead.ssh2.transport.KexManager.handleMessage(KexManager.java:381)
25632 25667 E ReceiverCoroutine:    at com.trilead.ssh2.transport.TransportManager.receiveLoop(TransportManager.java:638)
25632 25667 E ReceiverCoroutine:    at com.trilead.ssh2.transport.TransportManager$1.run(TransportManager.java:4)
25632 25667 E ReceiverCoroutine:    at java.lang.Thread.run(Thread.java:923)
25632 25667 E ReceiverCoroutine: Caused by: java.security.InvalidKeyException: Banned public key: 0000000000000000000000000000000000000000000000000000000000000000
25632 25667 E ReceiverCoroutine:    at com.google.crypto.tink.subtle.X25519.computeSharedSecret(X25519.java:94)
25632 25667 E ReceiverCoroutine:    at com.trilead.ssh2.crypto.dh.Curve25519Exchange.init(Curve25519Exchange.java:75)
25632 25667 E ReceiverCoroutine:    ... 4 more
gujjwal00 commented 9 months ago

I tried v2.3.1 from F-Droid, but I can't reproduce this. I am able to connect with RSA & Ed25519 keys of different sizes. Can you please try this unoptimized APK: app-debug.zip

About that stacktrace: It looks impossible to me, unless R8 literally removed functioning code. Can you upload the APK you are testing with?

PureIncompetence commented 9 months ago

Thank you for your fast reaction.

I forgot to mention that I'm not using key-based but password-based ssh auth on the target system in question - just in case that matters. Edit: It doesn't seem to matter as a connection attempt with an ssh key (generated in termux) fails, too.

I tried with the debug-apk (once using password- and once using key-based auth) and it works perfectly fine. At connection attempt I'm getting the following prompt (not sure if thats new, since I didn't reinstall / re-add my servers for some time now): 2024-02-17 After accepting the connection works as it used to in previous versions.

The APK I'm using is the one from F-Droid. Since AVNCs APK is multi-arch you must have used the same one for testing as I did. I can still reupload it here nonetheless: com.gaurav.avnc_28.zip Edit: Just to rule out any possible incompatibillities introduced by F-Droids compiling process I tested with your APK from github releases and got the exact same result.

Yet another edit: I tried the F-Droid Build with another client device, running LineageOS 20 (Android 13). There it (ssh-tunnel with password-based auth) does work. The original client device in question is still running Android 11, so it seems to depend upon the android version. If you need any additional logs / information, please let me know. I'm happy to help debugging this issue.

gujjwal00 commented 9 months ago

I tried with the debug-apk (once using password- and once using key-based auth) and it works perfectly fine.

So maybe the optimizations done by R8 are causing trouble.

At connection attempt I'm getting the following prompt (not sure if thats new, since I didn't reinstall / re-add my servers for some time now):

Yeah, that's normal for first connection. It is similar to ~/.ssh/known_hosts file used by ssh client.

The APK I'm using is the one from F-Droid. Since AVNCs APK is multi-arch you must have used the same one for testing as I did. I can still reupload it here nonetheless: com.gaurav.avnc_28.zip Edit: Just to rule out any possible incompatibillities introduced by F-Droids compiling process I tested with your APK from github releases and got the exact same result.

Yeah, that rules out any APK related issue.

I tried the F-Droid Build with another client device, running LineageOS 20 (Android 13). There it (ssh-tunnel with password-based auth) does work. The original client device in question is still running Android 11, so it seems to depend upon the android version.

I have tested v2.3.1 to work correctly on following platforms:

If you need any additional logs / information, please let me know. I'm happy to help debugging this issue.

Different Android Gradle Plugin versions uses different versions of R8. Current v2.3.1 is built with AGP 8.2.0. So its possible that this particular version is the culprit.

I am attaching two optimized APKs here:

Please test with both of these. For the version you fail to connect successfully, please post the full logs (if you can use adb logcat that would be great, otherwise just use Logs dialog in AVNC settings).

PureIncompetence commented 9 months ago

Please test with both of these. For the version you fail to connect successfully, please post the full logs (if you can use adb logcat that would be great, otherwise just use Logs dialog in AVNC settings).

I tested both versions. Both fail to connect. (SSH-tunnel with password-auth) I tried to capture the related logcat events but as a grep for com.gaurav.avnc or its UID doesn't contain the actual java exception and the full, unfiltered logcat contains several tens of pages of unrelated info during the few seconds the test takes I can only provide the logs from the AVNC log dialog. If you can give me a hint how to filter (e.g. what to grep for) the logcat output for only the relevant information I would repeat the test to provide logcat output.

AVNC-Log of a connection attempt using app-ci-8.1.4.apk:

--------- beginning of main
23432 23467 E ReceiverCoroutine: Connection failed
23432 23467 E ReceiverCoroutine: java.io.IOException: There was a problem while connecting to 192.168.XXX.XX:22
23432 23467 E ReceiverCoroutine:    at com.trilead.ssh2.Connection.connect(Connection.java:149)
23432 23467 E ReceiverCoroutine:    at com.gaurav.avnc.viewmodel.VncViewModel.access$connect(VncViewModel.kt:56)
23432 23467 E ReceiverCoroutine:    at com.gaurav.avnc.viewmodel.VncViewModel$launchConnection$1.invokeSuspend(VncViewModel.kt:41)
23432 23467 E ReceiverCoroutine:    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:9)
23432 23467 E ReceiverCoroutine:    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:112)
23432 23467 E ReceiverCoroutine:    at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:4)
23432 23467 E ReceiverCoroutine:    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:3)
23432 23467 E ReceiverCoroutine:    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:96)
23432 23467 E ReceiverCoroutine: Caused by: java.io.IOException: Key exchange was not finished, connection is closed.
23432 23467 E ReceiverCoroutine:    at com.trilead.ssh2.transport.TransportManager.getConnectionInfo(TransportManager.java:37)
23432 23467 E ReceiverCoroutine:    at com.trilead.ssh2.Connection.connect(Connection.java:82)
23432 23467 E ReceiverCoroutine:    ... 7 more
23432 23467 E ReceiverCoroutine: Caused by: java.io.IOException: java.security.InvalidKeyException: Banned public key: 0000000000000000000000000000000000000000000000000000000000000000
23432 23467 E ReceiverCoroutine:    at com.trilead.ssh2.crypto.dh.Curve25519Exchange.init(Curve25519Exchange.java:85)
23432 23467 E ReceiverCoroutine:    at com.trilead.ssh2.transport.KexManager.handleMessage(KexManager.java:380)
23432 23467 E ReceiverCoroutine:    at com.trilead.ssh2.transport.TransportManager.receiveLoop(TransportManager.java:689)
23432 23467 E ReceiverCoroutine:    at com.trilead.ssh2.transport.TransportManager$1.run(TransportManager.java:4)
23432 23467 E ReceiverCoroutine:    at java.lang.Thread.run(Thread.java:923)
23432 23467 E ReceiverCoroutine: Caused by: java.security.InvalidKeyException: Banned public key: 0000000000000000000000000000000000000000000000000000000000000000
23432 23467 E ReceiverCoroutine:    at com.google.crypto.tink.subtle.X25519.computeSharedSecret(X25519.java:94)
23432 23467 E ReceiverCoroutine:    at com.trilead.ssh2.crypto.dh.Curve25519Exchange.init(Curve25519Exchange.java:75)
23432 23467 E ReceiverCoroutine:    ... 4 more

AVNC-Log of a connection attempt using app-ci-8.2.2.apk:

--------- beginning of main
25629 25664 E ReceiverCoroutine: Connection failed
25629 25664 E ReceiverCoroutine: java.io.IOException: There was a problem while connecting to 192.168.XXX.XX:22
25629 25664 E ReceiverCoroutine:    at com.trilead.ssh2.Connection.connect(Connection.java:154)
25629 25664 E ReceiverCoroutine:    at com.gaurav.avnc.viewmodel.VncViewModel.access$connect(VncViewModel.kt:56)
25629 25664 E ReceiverCoroutine:    at com.gaurav.avnc.viewmodel.VncViewModel$launchConnection$1.invokeSuspend(VncViewModel.kt:41)
25629 25664 E ReceiverCoroutine:    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:9)
25629 25664 E ReceiverCoroutine:    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:111)
25629 25664 E ReceiverCoroutine:    at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:4)
25629 25664 E ReceiverCoroutine:    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:3)
25629 25664 E ReceiverCoroutine:    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:94)
25629 25664 E ReceiverCoroutine: Caused by: java.io.IOException: Key exchange was not finished, connection is closed.
25629 25664 E ReceiverCoroutine:    at com.trilead.ssh2.transport.TransportManager.getConnectionInfo(TransportManager.java:39)
25629 25664 E ReceiverCoroutine:    at com.trilead.ssh2.Connection.connect(Connection.java:79)
25629 25664 E ReceiverCoroutine:    ... 7 more
25629 25664 E ReceiverCoroutine: Caused by: java.io.IOException: java.security.InvalidKeyException: Banned public key: 0000000000000000000000000000000000000000000000000000000000000000
25629 25664 E ReceiverCoroutine:    at com.trilead.ssh2.crypto.dh.Curve25519Exchange.init(Curve25519Exchange.java:85)
25629 25664 E ReceiverCoroutine:    at com.trilead.ssh2.transport.KexManager.handleMessage(KexManager.java:381)
25629 25664 E ReceiverCoroutine:    at com.trilead.ssh2.transport.TransportManager.receiveLoop(TransportManager.java:638)
25629 25664 E ReceiverCoroutine:    at com.trilead.ssh2.transport.TransportManager$1.run(TransportManager.java:4)
25629 25664 E ReceiverCoroutine:    at java.lang.Thread.run(Thread.java:923)
25629 25664 E ReceiverCoroutine: Caused by: java.security.InvalidKeyException: Banned public key: 0000000000000000000000000000000000000000000000000000000000000000
25629 25664 E ReceiverCoroutine:    at com.google.crypto.tink.subtle.X25519.computeSharedSecret(X25519.java:94)
25629 25664 E ReceiverCoroutine:    at com.trilead.ssh2.crypto.dh.Curve25519Exchange.init(Curve25519Exchange.java:75)
25629 25664 E ReceiverCoroutine:    ... 4 more
25629 25651 W System  : A resource failed to call release. 
gujjwal00 commented 9 months ago

If you can give me a hint how to filter (e.g. what to grep for) the logcat output for only the relevant information I would repeat the test to provide logcat output.

Well, the test didn't work so just AVNC log is fine for now.

Please test these: app-ci-ssh21.zip app-ci-ssh23.zip

These have different SSH library versions. I think atleast the first one will work correctly.

PureIncompetence commented 9 months ago

With app-ci-ssh21 the connection works, The attempt with app-ci-ssh23 results in the error Disconnected.

app-ci-ssh21

--------- beginning of main
26292 26330 E SSHTest : false
26292 26330 E SSHTest : redacted
26292 26330 E SSHTest : redacted
26292 26903 I EngineFactory: Provider GmsCore_OpenSSL not available
26292 26326 D OpenGLRenderer: endAllActiveAnimators on 0xb400007543f12410 (RippleDrawable) with handle 0xb400007463e18b60
26292 26330 I NativeVnc: Received protocol version 3.8
26292 26330 I NativeVnc: VNC server supports protocol version 3.8 (viewer 3.8)
26292 26330 I NativeVnc: We have 1 security types to read
26292 26330 I NativeVnc: 0) Received security type 2
26292 26330 I NativeVnc: Selecting security type 2 (0/1 in the list)
26292 26330 I NativeVnc: Selected Security Scheme 2
26292 26330 I NativeVnc: VNC authentication succeeded
26292 26330 I NativeVnc: Desktop name "redacted:0"
26292 26330 I NativeVnc: Connected to VNC server, using protocol version 3.8
26292 26330 I NativeVnc: VNC server default format:
26292 26330 I NativeVnc:   32 bits per pixel.
26292 26330 I NativeVnc:   Least significant byte first in each pixel.
26292 26330 I NativeVnc:   TRUE colour: max red 255 green 255 blue 255, shift red 16 green 8 blue 0
26292 26901 D ShaderCompiler: 
26292 26901 I chatty  : uid=10582(com.gaurav.avnc.ci) identical 1 line
26292 26901 D ShaderCompiler: 
26292 26330 I NativeVnc: client2server supported messages (bit flags)
26292 26330 I NativeVnc: 00: 00ff 0081 0000 0000 - 0000 0000 0000 0000
26292 26330 I NativeVnc: 08: 0000 0000 0000 0000 - 0000 0000 0000 0000
26292 26330 I NativeVnc: 10: 0000 0000 0000 0000 - 0000 0000 0000 0000
26292 26330 I NativeVnc: 18: 0000 0000 0000 0000 - 0000 0000 0000 0008
26292 26330 I NativeVnc: server2client supported messages (bit flags)
26292 26330 I NativeVnc: 00: 001f 0080 0000 0000 - 0000 0000 0000 0000
26292 26330 I NativeVnc: 08: 0000 0000 0000 0000 - 0000 0000 0000 0000
26292 26330 I NativeVnc: 10: 0000 0000 0000 0000 - 0000 0000 0000 0000
26292 26330 I NativeVnc: 18: 0000 0000 0000 0000 - 0000 0000 0000 0000
26292 26330 I NativeVnc: Connected to Server "unknown (LibVNCServer 0.9.13)"

app-ci-ssh23

--------- beginning of main
30257 30301 E SSHTest : true
30257 30301 E ReceiverCoroutine: Connection failed
30257 30301 E ReceiverCoroutine: java.security.InvalidKeyException: Banned public key: 0000000000000000000000000000000000000000000000000000000000000000
30257 30301 E ReceiverCoroutine:    at com.google.crypto.tink.subtle.X25519.computeSharedSecret(X25519.java:94)
30257 30301 E ReceiverCoroutine:    at com.gaurav.avnc.viewmodel.VncViewModel.access$connect(VncViewModel.kt:53)
30257 30301 E ReceiverCoroutine:    at com.gaurav.avnc.viewmodel.VncViewModel$launchConnection$1.invokeSuspend(VncViewModel.kt:41)
30257 30301 E ReceiverCoroutine:    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:9)
30257 30301 E ReceiverCoroutine:    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:111)
30257 30301 E ReceiverCoroutine:    at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:4)
30257 30301 E ReceiverCoroutine:    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:3)
30257 30301 E ReceiverCoroutine:    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:94)
gujjwal00 commented 9 months ago

Ok, now we know the problematic combination: The issue happens with sshlib 2.2.23, when compiled with AGP 8.1+, with optimizations ON, on some specific devices.

About the issue: sshlib uses tink crypto library. It was updated in v2.2.23. One of the things that changed is how two byte arrays are compared. Newer version uses Java's MessageDigest.isEqual() method, instead of custom check. For some inexplicable reason, this method is returning true when comparing two different arrays. To test this I added a test in previous APKs. Look at the first line of logs for app-ci-ssh23:

30257 30301 E SSHTest : true

This should have been false like in case of app-ci-ssh21. This incorrect behavior trips up the tink library during key exchange, and a value which should not have been equal to, ends up being equal to 0000000000000000000000000000000000000000000000000000000000000000, resulting in that exception.

I am adding couple more APKs, with a bit more testing. Please post the logs for these: app-ci-test.zip app-debug-test.zip

gujjwal00 commented 9 months ago

Also, please tell me your device model and more about Android version (is it a custom ROM like MIUI or OneUI?)

PureIncompetence commented 9 months ago

app-ci.apk (failed with Disconnected):

--------- beginning of main
32332 32378 D SSHTest : b1 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32332 32378 D SSHTest : b2 = [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32332 32378 D SSHTest : MessageDigest.isEqual(b1, b2) = true
32332 32378 D SSHTest : Bytes.equal(b1, b2) = true
32332 32378 D SSHTest : d = [20 numbers between -119 and 118, redacted]
32332 32378 D SSHTest : 2nd MessageDigest.isEqual(b1, b2) = true
32332 32378 D SSHTest : 2nd Bytes.equal(b1, b2) = true
32332 32378 E ReceiverCoroutine: Connection failed
32332 32378 E ReceiverCoroutine: java.security.InvalidKeyException: Banned public key: 0000000000000000000000000000000000000000000000000000000000000000
32332 32378 E ReceiverCoroutine:    at com.google.crypto.tink.subtle.X25519.computeSharedSecret(X25519.java:94)
32332 32378 E ReceiverCoroutine:    at com.gaurav.avnc.viewmodel.VncViewModel.access$connect(VncViewModel.kt:193)
32332 32378 E ReceiverCoroutine:    at com.gaurav.avnc.viewmodel.VncViewModel$launchConnection$1.invokeSuspend(VncViewModel.kt:41)
32332 32378 E ReceiverCoroutine:    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:9)
32332 32378 E ReceiverCoroutine:    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:111)
32332 32378 E ReceiverCoroutine:    at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:4)
32332 32378 E ReceiverCoroutine:    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:3)
32332 32378 E ReceiverCoroutine:    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:94)

app-debug-test (connection works):

--------- beginning of main
4813  4860 D SSHTest : b1 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
4813  4860 D SSHTest : b2 = [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
4813  4860 D SSHTest : MessageDigest.isEqual(b1, b2) = false
4813  4860 D SSHTest : Bytes.equal(b1, b2) = false
4813  4860 D SSHTest : d = [20 numbers between -119 and 118, redacted]
4813  4860 D SSHTest : 2nd MessageDigest.isEqual(b1, b2) = false
4813  4860 D SSHTest : 2nd Bytes.equal(b1, b2) = false
4813  4860 E SSHTest : [redacted
4813  4860 E SSHTest : [redacted
4813  4856 D OpenGLRenderer: endAllActiveAnimators on 0xb400007543ece550 (RippleDrawable) with handle 0xb400007463e16ee0
4813  4860 I NativeVnc: Received protocol version 3.8
4813  4860 I NativeVnc: VNC server supports protocol version 3.8 (viewer 3.8)
4813  4860 I NativeVnc: We have 1 security types to read
4813  4860 I NativeVnc: 0) Received security type 2
4813  4860 I NativeVnc: Selecting security type 2 (0/1 in the list)
4813  4860 I NativeVnc: Selected Security Scheme 2
4813  4860 I NativeVnc: VNC authentication succeeded
4813  4860 I NativeVnc: Desktop name "redacted:0"
4813  4860 I NativeVnc: Connected to VNC server, using protocol version 3.8
4813  4860 I NativeVnc: VNC server default format:
4813  4860 I NativeVnc:   32 bits per pixel.
4813  4860 I NativeVnc:   Least significant byte first in each pixel.
4813  4860 I NativeVnc:   TRUE colour: max red 255 green 255 blue 255, shift red 16 green 8 blue 0
4813  5157 D ShaderCompiler: 
4813  5157 I chatty  : uid=10586(com.gaurav.avnc.debug) identical 1 line
4813  5157 D ShaderCompiler: 
4813  5157 D ShaderCompiler: Program [3] validation result: 1
4813  5157 D ShaderCompiler: Program [3] validation log: 
4813  4860 I NativeVnc: client2server supported messages (bit flags)
4813  4860 I NativeVnc: 00: 00ff 0081 0000 0000 - 0000 0000 0000 0000
4813  4860 I NativeVnc: 08: 0000 0000 0000 0000 - 0000 0000 0000 0000
4813  4860 I NativeVnc: 10: 0000 0000 0000 0000 - 0000 0000 0000 0000
4813  4860 I NativeVnc: 18: 0000 0000 0000 0000 - 0000 0000 0000 0008
4813  4860 I NativeVnc: server2client supported messages (bit flags)
4813  4860 I NativeVnc: 00: 001f 0080 0000 0000 - 0000 0000 0000 0000
4813  4860 I NativeVnc: 08: 0000 0000 0000 0000 - 0000 0000 0000 0000
4813  4860 I NativeVnc: 10: 0000 0000 0000 0000 - 0000 0000 0000 0000
4813  4860 I NativeVnc: 18: 0000 0000 0000 0000 - 0000 0000 0000 0000
4813  4860 I NativeVnc: Connected to Server "unknown (LibVNCServer 0.9.13)"

The device which on which the problems occur, is running an OmniROM-Build from mid 2021, based on Android 11. The device manufacturer / model seems irrelevant to me, as it ain't running the manufacturer-provided stock ROM anymore. For comparison: The device it works on is running a much more recent LineageOS 20 version from early 2024, based on Android 13. Edit: I additionally borrowed an android tablet running a LineageOS 18.1 version from august 2021, based on Android 11 and tested your latest app-ci.apk on it to rule out any OmniROM-specific incompatibillities. It behaves identically. (Disconnected) Here are the logs of testing the app-ci.apk on LineageOS 18.1:

--------- beginning of main
6794  6823 D SSHTest : b1 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
6794  6823 D SSHTest : b2 = [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
6794  6823 D SSHTest : MessageDigest.isEqual(b1, b2) = true
6794  6823 D SSHTest : Bytes.equal(b1, b2) = true
6794  6823 D SSHTest : d = [20 numbers between -119 and 118, redacted]
6794  6823 D SSHTest : 2nd MessageDigest.isEqual(b1, b2) = true
6794  6823 D SSHTest : 2nd Bytes.equal(b1, b2) = true
6794  6823 E ReceiverCoroutine: Connection failed
6794  6823 E ReceiverCoroutine: java.security.InvalidKeyException: Banned public key: 0000000000000000000000000000000000000000000000000000000000000000
6794  6823 E ReceiverCoroutine:     at com.google.crypto.tink.subtle.X25519.computeSharedSecret(X25519.java:94)
6794  6823 E ReceiverCoroutine:     at com.gaurav.avnc.viewmodel.VncViewModel.access$connect(VncViewModel.kt:193)
6794  6823 E ReceiverCoroutine:     at com.gaurav.avnc.viewmodel.VncViewModel$launchConnection$1.invokeSuspend(VncViewModel.kt:41)
6794  6823 E ReceiverCoroutine:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:9)
6794  6823 E ReceiverCoroutine:     at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:111)
6794  6823 E ReceiverCoroutine:     at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:4)
6794  6823 E ReceiverCoroutine:     at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:3)
6794  6823 E ReceiverCoroutine:     at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:94)
6794  6808 I .gaurav.avnc.c: Background concurrent copying GC freed 78363(3802KB) AllocSpace objects, 4(80KB) LOS objects, 51% free, 5868KB/11MB, paused 500us total 103.379ms
6794  6810 W System  : A resource failed to call release. 
gujjwal00 commented 9 months ago
6794  6823 D SSHTest : b1 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
6794  6823 D SSHTest : b2 = [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
6794  6823 D SSHTest : MessageDigest.isEqual(b1, b2) = true
6

This is the problematic part. As you can see, b1 & b2 byte arrays are different at first byte, but MessageDigest.isEqual(b1, b2) is returning true here. And it only seems to happen in optimized builds.

At this point, I need to reproduce this locally to be able to find the root cause. Thanks for testing all the APKs.

I don't want to downgrade sshlib because newer version includes fixes for terrapin attack.

6794  6823 D SSHTest : d = [20 numbers between -119 and 118, redacted]

Just as a note, this is SHA1 digest of byte array b2.

PureIncompetence commented 9 months ago

This is the problematic part. As you can see, b1 & b2 byte arrays are different at first byte, but MessageDigest.isEqual(b1, b2) is returning true here. And it only seems to happen in optimized builds. At this point, I need to reproduce this locally to be able to find the root cause. Thanks for testing all the APKs.

I see, thank you for investigating. I'm glad I could be of service. Afaik waydroid still uses an Android 11 based version of LineageOS. Maybe you could try reproducing it on waydroid, as the error does not occur on your Android 11 emulator? Edit: Nope. No need to go down that route. I tested the F-Droid 2.3.1 build on waydroid / LineageOS 11 and the bug didn't occur.

I don't want to downgrade sshlib because newer version includes fixes for terrapin attack.

Sounds reasonable to me.

Just as a note, this is SHA1 digest of byte array b2.

Yeah, I'm by no means an expert in all of this. I just wasn't sure if it was something along the lines of a password hash, so I just redacted it as a precaution.

gujjwal00 commented 8 months ago

After quite a bit of testing, I still can't reproduce it. Only reference I found to this bug is this issue: https://github.com/nightscout/AndroidAPS/issues/3039 . This too happens on Android 11, and on specific devices. If you check the logs linked there in first comment, you will find the same exception being thrown as we see here.

So, I want you to please run a few final tests before I chalk it up to an Android bug:

  1. These two are simplest possible APKs with zero dependencies. Theey just contain a byte array equality check, and will print the result on screen. simple-debug.zip simple-release.zip
  2. If both of those report success, then please try to log into ssh server via ConnectBot app. AVNC uses ConnectBot's SSH library, and if ConnectBot is working then we need to figure out why only AVNC is triggering this.
  3. Finally, test this version of AVNC: avnc-ci-keeprules.zip I added a few more R8 keep rules after looking through ConnectBot's sources.
PureIncompetence commented 8 months ago

simple-debug.apk

Debug version
Success: Arrays.equals works as expected
Success: MessageDigest works as expected

simple-release.apk

Release Version
Success: Arrays.equals works as expected
Error: MessageDigest.isEqual(b1, b2) is true

Even though the output of simple-release.apk certainly does not look like a success I still tried to connect with ConnectBot and got the exact same exception:

Key exchange was not finished, connection is closed.
java.security.InvalidKeyException: Banned public key: 000000000
00000000000000000000000000000000000000
Banned public key: 00000000000000000000000000000000000000000000
000

avnc-ci-keeprules.apk

--------- beginning of main
02-24 14:58:52.069  4808  4854 D SSHTest : b1 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
02-24 14:58:52.069  4808  4854 D SSHTest : b2 = [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
02-24 14:58:52.069  4808  4854 D SSHTest : b1.contentEquals(b2) = false
02-24 14:58:52.072  4808  4854 D SSHTest : MessageDigest.isEqual(b1, b2) = true
02-24 14:58:52.072  4808  4854 D SSHTest : Bytes.equal(b1, b2) = true
02-24 14:58:52.077  4808  4854 E ReceiverCoroutine: Connection failed
02-24 14:58:52.077  4808  4854 E ReceiverCoroutine: java.security.InvalidKeyException: Banned public key: 0000000000000000000000000000000000000000000000000000000000000000
02-24 14:58:52.077  4808  4854 E ReceiverCoroutine:     at com.google.crypto.tink.subtle.X25519.computeSharedSecret(X25519.java:94)
02-24 14:58:52.077  4808  4854 E ReceiverCoroutine:     at com.gaurav.avnc.viewmodel.VncViewModel.access$connect(VncViewModel.kt:141)
02-24 14:58:52.077  4808  4854 E ReceiverCoroutine:     at com.gaurav.avnc.viewmodel.VncViewModel$launchConnection$1.invokeSuspend(VncViewModel.kt:41)
02-24 14:58:52.077  4808  4854 E ReceiverCoroutine:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:9)
02-24 14:58:52.077  4808  4854 E ReceiverCoroutine:     at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:111)
02-24 14:58:52.077  4808  4854 E ReceiverCoroutine:     at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:4)
02-24 14:58:52.077  4808  4854 E ReceiverCoroutine:     at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:3)
02-24 14:58:52.077  4808  4854 E ReceiverCoroutine:     at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:94)

[…] before I chalk it up to an Android bug

Am I interpreting this statement correctly, that this is a (probably rare) android bug which you can't affect, but which you also can't / won't work around somehow in AVNC's code? Because that would be a bummer for me and probably others who arent willing to replace perfectly working devices just because they were declared EOL by their manufacturers, as we would have to stay on AVNC version 2.2.3. Regardless I want to thank you for investigating this appearently rare bug. (And for developing this awesome piece of software, ofc!)

gujjwal00 commented 8 months ago

[…] before I chalk it up to an Android bug

Am I interpreting this statement correctly, that this is a (probably rare) android bug

Yes, that's right. Its an Android bug. For reference, this is the code contained in those simple APK:

 private void test() {
        byte[] b1 = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        byte[] b2 = new byte[]{9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        String arraysResult = Arrays.equals(b1, b2) ? "Error: Arrays.equals(b1, b2) is true" : "Success: Arrays.equals works as expected";
        String messageDigestResult = MessageDigest.isEqual(b1, b2) ? "Error: MessageDigest.isEqual(b1, b2) is true" : "Success: MessageDigest works as expected";
        String version = BuildConfig.DEBUG ? "Debug version" : "Release Version";
        textView.setText(version + "\n\n" + arraysResult + "\n\n" + messageDigestResult);
    }

MessageDigest.isEqual(b1, b2) & Arrays.equals(b1, b2) are both Java functions, and they should always return false for these arrays. There is no difference between two simple APKs, other than that the first APK is debugable. So I would guess that its a bug in JIT/pre-compilation.

which you can't affect, but which you also can't / won't work around somehow in AVNC's code?

Yes, there is no workaround other than downgrading sshlib to older version.

Because that would be a bummer for me and probably others who arent willing to replace perfectly working devices just because they were declared EOL by their manufacturers, as we would have to stay on AVNC version 2.2.3.

When I was looking at #81 , I briefly tried to find alternative ssh libraries, but I could not find anything better than Connectbot's library. Not to say Connectbot's sshlib is perfect. I have do shit like this, just to find a port for tunnel.

But recently, I have been using https://github.com/apache/mina-sshd as a dummy server for writing SSH tunnel tests in AVNC. So one possibility is to migrate to that for client side too. Its not flawless though. mina-sshd doesn't officially support Android, and I haven't looked closely at the client-side feature set (and that it doesn't trigger the same bug 😀).

PureIncompetence commented 8 months ago

Thanks for clarification and explaining the background in an understandable way. 👍

I'll probably stay on 2.2.3 on that device until it breaks (or you decide to switch to another ssh library), then. Not exactly great from a security point of view, but in terms of usabillity that is already a pretty good status quo imo.