mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-08 06:46:11 +00:00
fix: harden unknown file TLV parsing
This commit is contained in:
parent
46de1a3623
commit
1a792ecfec
@ -33,7 +33,15 @@ data class BitchatFilePacket(
|
||||
) {
|
||||
private enum class TLVType(val v: UByte) {
|
||||
FILE_NAME(0x01u), FILE_SIZE(0x02u), MIME_TYPE(0x03u), CONTENT(0x04u);
|
||||
companion object { fun from(value: UByte) = values().find { it.v == value } }
|
||||
companion object {
|
||||
fun from(value: UByte): TLVType? = when (value) {
|
||||
FILE_NAME.v -> FILE_NAME
|
||||
FILE_SIZE.v -> FILE_SIZE
|
||||
MIME_TYPE.v -> MIME_TYPE
|
||||
CONTENT.v -> CONTENT
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun encode(): ByteArray? {
|
||||
@ -98,7 +106,11 @@ data class BitchatFilePacket(
|
||||
var mime: String? = null
|
||||
var contentBytes: ByteArray? = null
|
||||
var skippedUnknownTLVs = 0
|
||||
while (off + 3 <= data.size) { // minimum TLV header size (type + 2 bytes length)
|
||||
while (off < data.size) {
|
||||
// Every TLV needs at least a type and a 2-byte length.
|
||||
// Reject a truncated trailing header instead of silently
|
||||
// accepting it, matching the iOS decoder.
|
||||
if (data.size - off < 3) return null
|
||||
// A null `t` is an unknown tag: read its length like any
|
||||
// other 2-byte TLV and skip its value, matching iOS.
|
||||
val t = TLVType.from(data[off].toUByte())
|
||||
@ -163,4 +175,3 @@ data class BitchatFilePacket(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.model.BitchatMessageType
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
@ -201,6 +202,25 @@ class FileTransferTest {
|
||||
assertEquals(content.size, decoded.content.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decode should reject an incomplete TLV header after an unknown extension`() {
|
||||
// Given: a valid file and unknown extension followed by either only a
|
||||
// tag or a tag plus one length byte.
|
||||
val content = ByteArray(8) { 0x2A }
|
||||
val fileName = "truncated.bin".toByteArray(Charsets.UTF_8)
|
||||
val buf = ByteBuffer.allocate(
|
||||
(1 + 2 + fileName.size) + (1 + 4 + content.size) + (1 + 2)
|
||||
).order(ByteOrder.BIG_ENDIAN)
|
||||
buf.put(0x01.toByte()); buf.putShort(fileName.size.toShort()); buf.put(fileName)
|
||||
buf.put(0x04.toByte()); buf.putInt(content.size); buf.put(content)
|
||||
buf.put(0x05.toByte()); buf.putShort(0)
|
||||
val packetWithUnknownExtension = buf.array()
|
||||
|
||||
// When/Then: Android rejects the same incomplete tails that iOS does.
|
||||
assertNull(BitchatFilePacket.decode(packetWithUnknownExtension + byteArrayOf(0x06)))
|
||||
assertNull(BitchatFilePacket.decode(packetWithUnknownExtension + byteArrayOf(0x06, 0x00)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decode should handle a packet padded with many zero-length unknown TLVs`() {
|
||||
// Given: the cheapest padding a peer can send — a zero-length unknown
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user