Skip to content

Commit c5c5d9f

Browse files
committed
Prohibited using of zero and negative filed number in ProtoNumber and zero field numbers in input bytes
Fixes #1566
1 parent b712494 commit c5c5d9f

File tree

7 files changed

+90
-15
lines changed

7 files changed

+90
-15
lines changed

formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/Helpers.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ internal fun SerialDescriptor.extractParameters(index: Int): ProtoDesc {
8787
val annotation = annotations[i]
8888
if (annotation is ProtoNumber) {
8989
protoId = annotation.number
90+
checkFieldNumber(protoId, this)
9091
} else if (annotation is ProtoType) {
9192
format = annotation.type
9293
} else if (annotation is ProtoPacked) {
@@ -118,11 +119,21 @@ internal fun extractProtoId(descriptor: SerialDescriptor, index: Int, zeroBasedD
118119
return ID_HOLDER_ONE_OF
119120
} else if (annotation is ProtoNumber) {
120121
result = annotation.number
122+
// 0 or negative numbers are acceptable for enums
123+
if (!zeroBasedDefault) {
124+
checkFieldNumber(result, descriptor)
125+
}
121126
}
122127
}
123128
return result
124129
}
125130

131+
private fun checkFieldNumber(fieldNumber: Int, descriptor: SerialDescriptor) {
132+
if (fieldNumber <= 0) {
133+
throw SerializationException("$fieldNumber is not allowed in ProtoNumber for ${descriptor.serialName}, because protobuf support field values in range 1..2147483647")
134+
}
135+
}
136+
126137
internal class ProtobufDecodingException(message: String, e: Throwable? = null) : SerializationException(message, e)
127138

128139
internal expect fun Int.reverseBytes(): Int

formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufDecoding.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@ internal open class ProtobufDecoder(
311311
if (protoId == -1) { // EOF
312312
return elementMarker.nextUnmarkedIndex()
313313
}
314+
if (protoId == 0) {
315+
throw SerializationException("0 is not allowed as the protobuf field number in ${descriptor.serialName}, the input bytes may have been corrupted")
316+
}
314317
val index = getIndexByNum(protoId)
315318
if (index == -1) { // not found
316319
reader.skipElement()

formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufReader.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ internal class ProtobufReader(private val input: ByteArrayInput) {
7979
assertWireType(SIZE_DELIMITED)
8080
val length = decode32()
8181
checkLength(length)
82-
input.scipExactNBytes(length)
82+
input.skipExactNBytes(length)
8383
}
8484

8585
fun readByteArrayNoTag(): ByteArray {

formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/Streams.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ internal class ByteArrayInput(private var array: ByteArray, private val endIndex
3434
}
3535

3636

37-
fun scipExactNBytes(bytesCount: Int) {
37+
fun skipExactNBytes(bytesCount: Int) {
3838
ensureEnoughBytes(bytesCount)
3939
position += bytesCount
4040
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.serialization.protobuf
6+
7+
8+
import kotlinx.serialization.*
9+
import kotlin.test.*
10+
11+
class InvalidFieldNumberTest {
12+
13+
@Serializable
14+
data class Holder(val value: Int)
15+
16+
@Serializable
17+
data class ListHolder(val value: List<Int>)
18+
19+
@Serializable
20+
data class ZeroProtoNumber(@ProtoNumber(0) val value: Int)
21+
22+
@Serializable
23+
data class NegativeProtoNumber(@ProtoNumber(-5) val value: Int)
24+
25+
@Test
26+
fun testDeserializeZeroInput() {
27+
assertFailsWithMessage<SerializationException>("0 is not allowed as the protobuf field number in kotlinx.serialization.protobuf.InvalidFieldNumberTest.Holder, the input bytes may have been corrupted") {
28+
// first value with field number = 0
29+
val hexString = "000f"
30+
ProtoBuf.decodeFromHexString<Holder>(hexString)
31+
}
32+
}
33+
34+
@Test
35+
fun testDeserializeZeroInputForElement() {
36+
assertFailsWithMessage<SerializationException>("0 is not allowed as the protobuf field number in kotlinx.serialization.protobuf.InvalidFieldNumberTest.ListHolder, the input bytes may have been corrupted") {
37+
// first element with field number = 0
38+
val hexString = "000f"
39+
ProtoBuf.decodeFromHexString<ListHolder>(hexString)
40+
}
41+
}
42+
43+
@Test
44+
fun testSerializeZeroProtoNumber() {
45+
assertFailsWithMessage<SerializationException>("0 is not allowed in ProtoNumber for kotlinx.serialization.protobuf.InvalidFieldNumberTest.ZeroProtoNumber, because protobuf support field values in range 1..2147483647") {
46+
ProtoBuf.encodeToHexString(ZeroProtoNumber(42))
47+
}
48+
}
49+
50+
@Test
51+
fun testDeserializeZeroProtoNumber() {
52+
assertFailsWithMessage<SerializationException>("0 is not allowed in ProtoNumber for kotlinx.serialization.protobuf.InvalidFieldNumberTest.ZeroProtoNumber, because protobuf support field values in range 1..2147483647") {
53+
ProtoBuf.decodeFromHexString<ZeroProtoNumber>("000f")
54+
}
55+
}
56+
57+
@Test
58+
fun testSerializeNegativeProtoNumber() {
59+
assertFailsWithMessage<SerializationException>("-5 is not allowed in ProtoNumber for kotlinx.serialization.protobuf.InvalidFieldNumberTest.NegativeProtoNumber, because protobuf support field values in range 1..2147483647") {
60+
ProtoBuf.encodeToHexString(NegativeProtoNumber(42))
61+
}
62+
}
63+
64+
@Test
65+
fun testDeserializeNegativeProtoNumber() {
66+
assertFailsWithMessage<SerializationException>("-5 is not allowed in ProtoNumber for kotlinx.serialization.protobuf.InvalidFieldNumberTest.NegativeProtoNumber, because protobuf support field values in range 1..2147483647") {
67+
ProtoBuf.decodeFromHexString<NegativeProtoNumber>("000f")
68+
}
69+
}
70+
}

formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/PackedArraySerializerTest.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ class PackedArraySerializerTest {
4747

4848
@Serializable
4949
data class PackedStringCarrier(
50-
@ProtoNumber(0)
5150
@ProtoPacked
5251
val s: List<String>
5352
)
@@ -110,12 +109,12 @@ class PackedArraySerializerTest {
110109
@Test
111110
fun testEncodeAnnotatedStringList() {
112111
val obj = PackedStringCarrier(listOf("aaa", "bbb", "ccc"))
113-
val expectedHex = "020361616102036262620203636363"
112+
val expectedHex = "0a036161610a036262620a03636363"
114113
val encodedHex = ProtoBuf.encodeToHexString(obj)
115114
assertEquals(expectedHex, encodedHex)
116115
assertEquals(obj, ProtoBuf.decodeFromHexString<PackedStringCarrier>(expectedHex))
117116

118-
val invalidPackedHex = "020C036161610362626203636363"
117+
val invalidPackedHex = "0a0C036161610362626203636363"
119118
val decoded = ProtoBuf.decodeFromHexString<PackedStringCarrier>(invalidPackedHex)
120119
val invalidString = "\u0003aaa\u0003bbb\u0003ccc"
121120
assertEquals(PackedStringCarrier(listOf(invalidString)), decoded)

formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/SkipFieldsTest.kt

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,15 @@ class SkipFieldsTest {
1414
data class Holder(val value: Int)
1515

1616
@Test
17-
fun testSkipZeroId() {
18-
// first value with id = 0
19-
val hexString = "000f082a"
20-
val holder = ProtoBuf.decodeFromHexString<Holder>(hexString)
21-
assertEquals(42, holder.value)
22-
}
23-
24-
@Test
25-
fun testSkipBigId() {
17+
fun testSkipBigFieldNumber() {
2618
// first value with id = 2047 and takes 2 bytes
2719
val hexString = "f87f20082a"
2820
val holder = ProtoBuf.decodeFromHexString<Holder>(hexString)
2921
assertEquals(42, holder.value)
3022
}
3123

3224
@Test
33-
fun testSkipString() {
25+
fun testSkipUnknownFiledNumberForString() {
3426
// first value is size delimited (string) with id = 42
3527
val hexString = "d2020c48656c6c6f20576f726c6421082a"
3628
val holder = ProtoBuf.decodeFromHexString<Holder>(hexString)

0 commit comments

Comments
 (0)