fix: NCD UAF, conn_mgr invite protocol, node_id derivation, link reuse

node_conn_direct:
- ncd_init_cb: skip UP when links_up==0, ncd_up_cb delivers when link comes up
- close/force_close: handle_count safety checks, callback clearing
- open/open_node case 2: create links on reused dead conn
- ncd_create_links: skip duplicate link on same conn (stale->etcp==conn)
- set_callback: optional callback change on live handle

conn_mgr:
- cm_ncd_callback: DOWN only delivers event (handle stays alive)
- cm_ncd_callback: TIMEOUT force_close BEFORE cm_deliver_event
- cm_deliver_event: read handle fields to locals before callback (reentrany-safe)
- INVITE_INFO_REQ/RESP: add duplicate cmd byte for direct-send protocol
- cm_handle_invite_info_resp: search invite by group_id in channel conn_mgr

Android:
- node_id computed by sc_derive_node_id_from_pubkey in C, not Kotlin
- utun_bridge_join_channel: recompute node_id, log mismatch
- invite_link_c: compute nodeId from pubkey via sc_derive_node_id_from_pubkey
- NativeLib.deriveNodeId, InviteLink.kt uses C function
This commit is contained in:
evgeny committed 2026-08-03 09:51:06 +03:00
1 parent 4b07d59043
commit 740ed2a93a
10 files changed
+351 -53

No files matched your search

@@ -62,15 +62,6 @@ data class InviteData(
object InviteLink {
private const val PREFIX = "utun://"
fun deriveNodeId(pubkey: ByteArray): Long {
val md = MessageDigest.getInstance("SHA-256")
val hash = md.digest(pubkey)
var id = 0L
for (i in 0 until 8)
id = (id shl 8) or (hash[i].toLong() and 0xFF)
return id and 0x7FFFFFFFFFFFFFFFL
}
fun decode(link: String): InviteData? {
LogManager.addLog("DEBUG", "INVITE", "decode: ${link.take(80)}")
if (!link.startsWith(PREFIX)) {
@@ -140,7 +131,7 @@ object InviteLink {
}
if (pubkey == null || pubkey.size != INVITE_PUBKEY_SIZE) return null
val nodeId = deriveNodeId(pubkey)
val nodeId = NativeLib.deriveNodeId(pubkey)
val pkHex = pubkey.take(8).joinToString("") { "%02x".format(it) }
LogManager.addLog("INFO", "INVITE", "decode: ch=0x%016x node=0x%016x pk=%s... addrs=%d".format(
chId, nodeId, pkHex, addrs.size))
@@ -7,7 +7,6 @@ interface ChatEventCallback {
}
object NativeLib {
private var running = false
init {
System.loadLibrary("utun")
@@ -24,16 +23,13 @@ object NativeLib {
/** Start full uTun instance from INI config text */
fun start(configText: String): Boolean {
if (running) return true
val ok = nativeStart(configText)
if (ok) running = true
return ok
if (isReady()) return true
return nativeStart(configText)
}
fun stop() {
if (!running) return
if (!isReady()) return
nativeStop()
running = false
}
fun setEventCallback(cb: ChatEventCallback) {
@@ -42,16 +38,13 @@ object NativeLib {
})
}
fun destroy() {
nativeDestroy()
running = false
}
fun destroy() { nativeDestroy() }
fun restart(dbPath: String, controlPort: Int, configText: String): Boolean {
return nativeRestart(configText)
}
fun isReady(): Boolean = running
fun isReady(): Boolean = nativeIsRunning()
fun getMyNodeId(): Long = nativeGetMyNodeId()
@@ -66,6 +59,7 @@ object NativeLib {
nativeJoinChannel(channelId, nodeId, pubkey, addrs, addrCount)
LogManager.addLog("INFO", "JNI", "joinChannel: native returned")
}
fun deriveNodeId(pubkey: ByteArray): Long = nativeDeriveNodeId(pubkey)
fun connectChannel(channelId: String) { nativeConnectChannel(channelId) }
fun getChannels(): String = nativeGetChannels() ?: "[]"
fun getMessages(channelId: String, limit: Int = 50): String = nativeGetMessages(channelId, limit) ?: "[]"
@@ -124,6 +118,7 @@ object NativeLib {
private external fun nativeCreateChannel(name: String, channelId: String)
private external fun nativeConnectNode(address: String, port: Int, pubkeyHex: String)
private external fun nativeJoinChannel(channelId: Long, nodeId: Long, pubkey: ByteArray, addrs: ByteArray, addrCount: Int)
private external fun nativeDeriveNodeId(pubkey: ByteArray): Long
private external fun nativeConnectChannel(channelId: String)
private external fun nativeGetChannels(): String?
private external fun nativeGetMessages(channelId: String, limit: Int): String?
@@ -138,6 +133,7 @@ object NativeLib {
private external fun nativeRestart(configText: String): Boolean
private external fun nativePing()
private external fun nativeIsResponsive(): Boolean
private external fun nativeIsRunning(): Boolean
/* ── Channel members JNI ── */
private external fun nativeGetChannelMembers(channelId: String): String?
@@ -95,7 +95,7 @@ class ChatViewModel : ViewModel() {
viewModelScope.launch {
while (isActive) {
delay(3000)
if (!dbReady || !NativeLib.isReady()) continue
if (!dbReady || !NativeLib.isReady()) { _isStuck.value = true; continue }
NativeLib.ping()
delay(100)
_isStuck.value = !NativeLib.isResponsive()