diff options
-rw-r--r-- | meta-oe/recipes-devtools/protobuf/protobuf/0001-Add-recursion-check-when-parsing-unknown-fields-in-J.patch | 794 | ||||
-rw-r--r-- | meta-oe/recipes-devtools/protobuf/protobuf_4.25.8.bb (renamed from meta-oe/recipes-devtools/protobuf/protobuf_4.25.3.bb) | 3 |
2 files changed, 1 insertions, 796 deletions
diff --git a/meta-oe/recipes-devtools/protobuf/protobuf/0001-Add-recursion-check-when-parsing-unknown-fields-in-J.patch b/meta-oe/recipes-devtools/protobuf/protobuf/0001-Add-recursion-check-when-parsing-unknown-fields-in-J.patch deleted file mode 100644 index 2f14620b4a..0000000000 --- a/meta-oe/recipes-devtools/protobuf/protobuf/0001-Add-recursion-check-when-parsing-unknown-fields-in-J.patch +++ /dev/null | |||
@@ -1,794 +0,0 @@ | |||
1 | From 9f182ae260cc60e8cc5417abbe9481821642afa0 Mon Sep 17 00:00:00 2001 | ||
2 | From: Protobuf Team Bot <protobuf-github-bot@google.com> | ||
3 | Date: Tue, 17 Sep 2024 12:03:36 -0700 | ||
4 | Subject: [PATCH] Add recursion check when parsing unknown fields in Java. | ||
5 | |||
6 | PiperOrigin-RevId: 675657198 | ||
7 | |||
8 | CVE: CVE-2024-7254 | ||
9 | |||
10 | Upstream-Status: Backport [ac9fb5b4c71b0dd80985b27684e265d1f03abf46] | ||
11 | |||
12 | The original patch is adjusted to fit for the current version. | ||
13 | |||
14 | Signed-off-by: Chen Qi <Qi.Chen@windriver.com> | ||
15 | --- | ||
16 | .../com/google/protobuf/ArrayDecoders.java | 28 +++ | ||
17 | .../com/google/protobuf/CodedInputStream.java | 72 +++++- | ||
18 | .../com/google/protobuf/MessageSchema.java | 9 +- | ||
19 | .../com/google/protobuf/MessageSetSchema.java | 2 +- | ||
20 | .../google/protobuf/UnknownFieldSchema.java | 28 ++- | ||
21 | .../google/protobuf/CodedInputStreamTest.java | 159 ++++++++++++ | ||
22 | .../java/com/google/protobuf/LiteTest.java | 232 ++++++++++++++++++ | ||
23 | 7 files changed, 514 insertions(+), 16 deletions(-) | ||
24 | |||
25 | diff --git a/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java b/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java | ||
26 | index f3241de50..0f3d7de0d 100644 | ||
27 | --- a/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java | ||
28 | +++ b/java/core/src/main/java/com/google/protobuf/ArrayDecoders.java | ||
29 | @@ -26,6 +26,10 @@ final class ArrayDecoders { | ||
30 | |||
31 | private ArrayDecoders() { | ||
32 | } | ||
33 | + static final int DEFAULT_RECURSION_LIMIT = 100; | ||
34 | + | ||
35 | + @SuppressWarnings("NonFinalStaticField") | ||
36 | + private static volatile int recursionLimit = DEFAULT_RECURSION_LIMIT; | ||
37 | |||
38 | /** | ||
39 | * A helper used to return multiple values in a Java function. Java doesn't natively support | ||
40 | @@ -38,6 +42,7 @@ final class ArrayDecoders { | ||
41 | public long long1; | ||
42 | public Object object1; | ||
43 | public final ExtensionRegistryLite extensionRegistry; | ||
44 | + public int recursionDepth; | ||
45 | |||
46 | Registers() { | ||
47 | this.extensionRegistry = ExtensionRegistryLite.getEmptyRegistry(); | ||
48 | @@ -245,7 +250,10 @@ final class ArrayDecoders { | ||
49 | if (length < 0 || length > limit - position) { | ||
50 | throw InvalidProtocolBufferException.truncatedMessage(); | ||
51 | } | ||
52 | + registers.recursionDepth++; | ||
53 | + checkRecursionLimit(registers.recursionDepth); | ||
54 | schema.mergeFrom(msg, data, position, position + length, registers); | ||
55 | + registers.recursionDepth--; | ||
56 | registers.object1 = msg; | ||
57 | return position + length; | ||
58 | } | ||
59 | @@ -263,8 +271,11 @@ final class ArrayDecoders { | ||
60 | // A group field must has a MessageSchema (the only other subclass of Schema is MessageSetSchema | ||
61 | // and it can't be used in group fields). | ||
62 | final MessageSchema messageSchema = (MessageSchema) schema; | ||
63 | + registers.recursionDepth++; | ||
64 | + checkRecursionLimit(registers.recursionDepth); | ||
65 | final int endPosition = | ||
66 | messageSchema.parseMessage(msg, data, position, limit, endGroup, registers); | ||
67 | + registers.recursionDepth--; | ||
68 | registers.object1 = msg; | ||
69 | return endPosition; | ||
70 | } | ||
71 | @@ -1025,6 +1036,8 @@ final class ArrayDecoders { | ||
72 | final UnknownFieldSetLite child = UnknownFieldSetLite.newInstance(); | ||
73 | final int endGroup = (tag & ~0x7) | WireFormat.WIRETYPE_END_GROUP; | ||
74 | int lastTag = 0; | ||
75 | + registers.recursionDepth++; | ||
76 | + checkRecursionLimit(registers.recursionDepth); | ||
77 | while (position < limit) { | ||
78 | position = decodeVarint32(data, position, registers); | ||
79 | lastTag = registers.int1; | ||
80 | @@ -1033,6 +1046,7 @@ final class ArrayDecoders { | ||
81 | } | ||
82 | position = decodeUnknownField(lastTag, data, position, limit, child, registers); | ||
83 | } | ||
84 | + registers.recursionDepth--; | ||
85 | if (position > limit || lastTag != endGroup) { | ||
86 | throw InvalidProtocolBufferException.parseFailure(); | ||
87 | } | ||
88 | @@ -1079,4 +1093,18 @@ final class ArrayDecoders { | ||
89 | throw InvalidProtocolBufferException.invalidTag(); | ||
90 | } | ||
91 | } | ||
92 | + | ||
93 | + /** | ||
94 | + * Set the maximum recursion limit that ArrayDecoders will allow. An exception will be thrown if | ||
95 | + * the depth of the message exceeds this limit. | ||
96 | + */ | ||
97 | + public static void setRecursionLimit(int limit) { | ||
98 | + recursionLimit = limit; | ||
99 | + } | ||
100 | + | ||
101 | + private static void checkRecursionLimit(int depth) throws InvalidProtocolBufferException { | ||
102 | + if (depth >= recursionLimit) { | ||
103 | + throw InvalidProtocolBufferException.recursionLimitExceeded(); | ||
104 | + } | ||
105 | + } | ||
106 | } | ||
107 | diff --git a/java/core/src/main/java/com/google/protobuf/CodedInputStream.java b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java | ||
108 | index 8f1ac736d..29256b4b3 100644 | ||
109 | --- a/java/core/src/main/java/com/google/protobuf/CodedInputStream.java | ||
110 | +++ b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java | ||
111 | @@ -703,7 +703,14 @@ public abstract class CodedInputStream { | ||
112 | public void skipMessage() throws IOException { | ||
113 | while (true) { | ||
114 | final int tag = readTag(); | ||
115 | - if (tag == 0 || !skipField(tag)) { | ||
116 | + if (tag == 0) { | ||
117 | + return; | ||
118 | + } | ||
119 | + checkRecursionLimit(); | ||
120 | + ++recursionDepth; | ||
121 | + boolean fieldSkipped = skipField(tag); | ||
122 | + --recursionDepth; | ||
123 | + if (!fieldSkipped) { | ||
124 | return; | ||
125 | } | ||
126 | } | ||
127 | @@ -713,7 +720,14 @@ public abstract class CodedInputStream { | ||
128 | public void skipMessage(CodedOutputStream output) throws IOException { | ||
129 | while (true) { | ||
130 | final int tag = readTag(); | ||
131 | - if (tag == 0 || !skipField(tag, output)) { | ||
132 | + if (tag == 0) { | ||
133 | + return; | ||
134 | + } | ||
135 | + checkRecursionLimit(); | ||
136 | + ++recursionDepth; | ||
137 | + boolean fieldSkipped = skipField(tag, output); | ||
138 | + --recursionDepth; | ||
139 | + if (!fieldSkipped) { | ||
140 | return; | ||
141 | } | ||
142 | } | ||
143 | @@ -1415,7 +1429,14 @@ public abstract class CodedInputStream { | ||
144 | public void skipMessage() throws IOException { | ||
145 | while (true) { | ||
146 | final int tag = readTag(); | ||
147 | - if (tag == 0 || !skipField(tag)) { | ||
148 | + if (tag == 0) { | ||
149 | + return; | ||
150 | + } | ||
151 | + checkRecursionLimit(); | ||
152 | + ++recursionDepth; | ||
153 | + boolean fieldSkipped = skipField(tag); | ||
154 | + --recursionDepth; | ||
155 | + if (!fieldSkipped) { | ||
156 | return; | ||
157 | } | ||
158 | } | ||
159 | @@ -1425,7 +1446,14 @@ public abstract class CodedInputStream { | ||
160 | public void skipMessage(CodedOutputStream output) throws IOException { | ||
161 | while (true) { | ||
162 | final int tag = readTag(); | ||
163 | - if (tag == 0 || !skipField(tag, output)) { | ||
164 | + if (tag == 0) { | ||
165 | + return; | ||
166 | + } | ||
167 | + checkRecursionLimit(); | ||
168 | + ++recursionDepth; | ||
169 | + boolean fieldSkipped = skipField(tag, output); | ||
170 | + --recursionDepth; | ||
171 | + if (!fieldSkipped) { | ||
172 | return; | ||
173 | } | ||
174 | } | ||
175 | @@ -2180,7 +2208,14 @@ public abstract class CodedInputStream { | ||
176 | public void skipMessage() throws IOException { | ||
177 | while (true) { | ||
178 | final int tag = readTag(); | ||
179 | - if (tag == 0 || !skipField(tag)) { | ||
180 | + if (tag == 0) { | ||
181 | + return; | ||
182 | + } | ||
183 | + checkRecursionLimit(); | ||
184 | + ++recursionDepth; | ||
185 | + boolean fieldSkipped = skipField(tag); | ||
186 | + --recursionDepth; | ||
187 | + if (!fieldSkipped) { | ||
188 | return; | ||
189 | } | ||
190 | } | ||
191 | @@ -2190,7 +2225,14 @@ public abstract class CodedInputStream { | ||
192 | public void skipMessage(CodedOutputStream output) throws IOException { | ||
193 | while (true) { | ||
194 | final int tag = readTag(); | ||
195 | - if (tag == 0 || !skipField(tag, output)) { | ||
196 | + if (tag == 0) { | ||
197 | + return; | ||
198 | + } | ||
199 | + checkRecursionLimit(); | ||
200 | + ++recursionDepth; | ||
201 | + boolean fieldSkipped = skipField(tag, output); | ||
202 | + --recursionDepth; | ||
203 | + if (!fieldSkipped) { | ||
204 | return; | ||
205 | } | ||
206 | } | ||
207 | @@ -3298,7 +3340,14 @@ public abstract class CodedInputStream { | ||
208 | public void skipMessage() throws IOException { | ||
209 | while (true) { | ||
210 | final int tag = readTag(); | ||
211 | - if (tag == 0 || !skipField(tag)) { | ||
212 | + if (tag == 0) { | ||
213 | + return; | ||
214 | + } | ||
215 | + checkRecursionLimit(); | ||
216 | + ++recursionDepth; | ||
217 | + boolean fieldSkipped = skipField(tag); | ||
218 | + --recursionDepth; | ||
219 | + if (!fieldSkipped) { | ||
220 | return; | ||
221 | } | ||
222 | } | ||
223 | @@ -3308,7 +3357,14 @@ public abstract class CodedInputStream { | ||
224 | public void skipMessage(CodedOutputStream output) throws IOException { | ||
225 | while (true) { | ||
226 | final int tag = readTag(); | ||
227 | - if (tag == 0 || !skipField(tag, output)) { | ||
228 | + if (tag == 0) { | ||
229 | + return; | ||
230 | + } | ||
231 | + checkRecursionLimit(); | ||
232 | + ++recursionDepth; | ||
233 | + boolean fieldSkipped = skipField(tag, output); | ||
234 | + --recursionDepth; | ||
235 | + if (!fieldSkipped) { | ||
236 | return; | ||
237 | } | ||
238 | } | ||
239 | diff --git a/java/core/src/main/java/com/google/protobuf/MessageSchema.java b/java/core/src/main/java/com/google/protobuf/MessageSchema.java | ||
240 | index de3890f70..5ad6762b0 100644 | ||
241 | --- a/java/core/src/main/java/com/google/protobuf/MessageSchema.java | ||
242 | +++ b/java/core/src/main/java/com/google/protobuf/MessageSchema.java | ||
243 | @@ -3006,7 +3006,8 @@ final class MessageSchema<T> implements Schema<T> { | ||
244 | unknownFields = unknownFieldSchema.getBuilderFromMessage(message); | ||
245 | } | ||
246 | // Unknown field. | ||
247 | - if (unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) { | ||
248 | + if (unknownFieldSchema.mergeOneFieldFrom( | ||
249 | + unknownFields, reader, /* currentDepth= */ 0)) { | ||
250 | continue; | ||
251 | } | ||
252 | } | ||
253 | @@ -3381,7 +3382,8 @@ final class MessageSchema<T> implements Schema<T> { | ||
254 | if (unknownFields == null) { | ||
255 | unknownFields = unknownFieldSchema.getBuilderFromMessage(message); | ||
256 | } | ||
257 | - if (!unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) { | ||
258 | + if (!unknownFieldSchema.mergeOneFieldFrom( | ||
259 | + unknownFields, reader, /* currentDepth= */ 0)) { | ||
260 | return; | ||
261 | } | ||
262 | break; | ||
263 | @@ -3397,7 +3399,8 @@ final class MessageSchema<T> implements Schema<T> { | ||
264 | if (unknownFields == null) { | ||
265 | unknownFields = unknownFieldSchema.getBuilderFromMessage(message); | ||
266 | } | ||
267 | - if (!unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) { | ||
268 | + if (!unknownFieldSchema.mergeOneFieldFrom( | ||
269 | + unknownFields, reader, /* currentDepth= */ 0)) { | ||
270 | return; | ||
271 | } | ||
272 | } | ||
273 | diff --git a/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java b/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java | ||
274 | index eec3acd35..ec37d41f9 100644 | ||
275 | --- a/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java | ||
276 | +++ b/java/core/src/main/java/com/google/protobuf/MessageSetSchema.java | ||
277 | @@ -278,7 +278,7 @@ final class MessageSetSchema<T> implements Schema<T> { | ||
278 | reader, extension, extensionRegistry, extensions); | ||
279 | return true; | ||
280 | } else { | ||
281 | - return unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader); | ||
282 | + return unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader, /* currentDepth= */ 0); | ||
283 | } | ||
284 | } else { | ||
285 | return reader.skipField(); | ||
286 | diff --git a/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java b/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java | ||
287 | index c4ec645bf..0cdecd30e 100644 | ||
288 | --- a/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java | ||
289 | +++ b/java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java | ||
290 | @@ -13,6 +13,11 @@ import java.io.IOException; | ||
291 | @CheckReturnValue | ||
292 | abstract class UnknownFieldSchema<T, B> { | ||
293 | |||
294 | + static final int DEFAULT_RECURSION_LIMIT = 100; | ||
295 | + | ||
296 | + @SuppressWarnings("NonFinalStaticField") | ||
297 | + private static volatile int recursionLimit = DEFAULT_RECURSION_LIMIT; | ||
298 | + | ||
299 | /** Whether unknown fields should be dropped. */ | ||
300 | abstract boolean shouldDiscardUnknownFields(Reader reader); | ||
301 | |||
302 | @@ -56,7 +61,8 @@ abstract class UnknownFieldSchema<T, B> { | ||
303 | abstract void makeImmutable(Object message); | ||
304 | |||
305 | /** Merges one field into the unknown fields. */ | ||
306 | - final boolean mergeOneFieldFrom(B unknownFields, Reader reader) throws IOException { | ||
307 | + final boolean mergeOneFieldFrom(B unknownFields, Reader reader, int currentDepth) | ||
308 | + throws IOException { | ||
309 | int tag = reader.getTag(); | ||
310 | int fieldNumber = WireFormat.getTagFieldNumber(tag); | ||
311 | switch (WireFormat.getTagWireType(tag)) { | ||
312 | @@ -75,7 +81,12 @@ abstract class UnknownFieldSchema<T, B> { | ||
313 | case WireFormat.WIRETYPE_START_GROUP: | ||
314 | final B subFields = newBuilder(); | ||
315 | int endGroupTag = WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP); | ||
316 | - mergeFrom(subFields, reader); | ||
317 | + currentDepth++; | ||
318 | + if (currentDepth >= recursionLimit) { | ||
319 | + throw InvalidProtocolBufferException.recursionLimitExceeded(); | ||
320 | + } | ||
321 | + mergeFrom(subFields, reader, currentDepth); | ||
322 | + currentDepth--; | ||
323 | if (endGroupTag != reader.getTag()) { | ||
324 | throw InvalidProtocolBufferException.invalidEndTag(); | ||
325 | } | ||
326 | @@ -88,10 +99,11 @@ abstract class UnknownFieldSchema<T, B> { | ||
327 | } | ||
328 | } | ||
329 | |||
330 | - final void mergeFrom(B unknownFields, Reader reader) throws IOException { | ||
331 | + final void mergeFrom(B unknownFields, Reader reader, int currentDepth) | ||
332 | + throws IOException { | ||
333 | while (true) { | ||
334 | if (reader.getFieldNumber() == Reader.READ_DONE | ||
335 | - || !mergeOneFieldFrom(unknownFields, reader)) { | ||
336 | + || !mergeOneFieldFrom(unknownFields, reader, currentDepth)) { | ||
337 | break; | ||
338 | } | ||
339 | } | ||
340 | @@ -108,4 +120,12 @@ abstract class UnknownFieldSchema<T, B> { | ||
341 | abstract int getSerializedSizeAsMessageSet(T message); | ||
342 | |||
343 | abstract int getSerializedSize(T unknowns); | ||
344 | + | ||
345 | + /** | ||
346 | + * Set the maximum recursion limit that ArrayDecoders will allow. An exception will be thrown if | ||
347 | + * the depth of the message exceeds this limit. | ||
348 | + */ | ||
349 | + public void setRecursionLimit(int limit) { | ||
350 | + recursionLimit = limit; | ||
351 | + } | ||
352 | } | ||
353 | diff --git a/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java b/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java | ||
354 | index 2de3273e3..19a6b669d 100644 | ||
355 | --- a/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java | ||
356 | +++ b/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java | ||
357 | @@ -10,6 +10,10 @@ package com.google.protobuf; | ||
358 | import static com.google.common.truth.Truth.assertThat; | ||
359 | import static com.google.common.truth.Truth.assertWithMessage; | ||
360 | import static org.junit.Assert.assertArrayEquals; | ||
361 | +import static org.junit.Assert.assertThrows; | ||
362 | + | ||
363 | +import com.google.common.primitives.Bytes; | ||
364 | +import map_test.MapTestProto.MapContainer; | ||
365 | import protobuf_unittest.UnittestProto.BoolMessage; | ||
366 | import protobuf_unittest.UnittestProto.Int32Message; | ||
367 | import protobuf_unittest.UnittestProto.Int64Message; | ||
368 | @@ -34,6 +38,13 @@ public class CodedInputStreamTest { | ||
369 | |||
370 | private static final int DEFAULT_BLOCK_SIZE = 4096; | ||
371 | |||
372 | + private static final int GROUP_TAP = WireFormat.makeTag(3, WireFormat.WIRETYPE_START_GROUP); | ||
373 | + | ||
374 | + private static final byte[] NESTING_SGROUP = generateSGroupTags(); | ||
375 | + | ||
376 | + private static final byte[] NESTING_SGROUP_WITH_INITIAL_BYTES = generateSGroupTagsForMapField(); | ||
377 | + | ||
378 | + | ||
379 | private enum InputType { | ||
380 | ARRAY { | ||
381 | @Override | ||
382 | @@ -116,6 +127,17 @@ public class CodedInputStreamTest { | ||
383 | return bytes; | ||
384 | } | ||
385 | |||
386 | + private static byte[] generateSGroupTags() { | ||
387 | + byte[] bytes = new byte[100000]; | ||
388 | + Arrays.fill(bytes, (byte) GROUP_TAP); | ||
389 | + return bytes; | ||
390 | + } | ||
391 | + | ||
392 | + private static byte[] generateSGroupTagsForMapField() { | ||
393 | + byte[] initialBytes = {18, 1, 75, 26, (byte) 198, (byte) 154, 12}; | ||
394 | + return Bytes.concat(initialBytes, NESTING_SGROUP); | ||
395 | + } | ||
396 | + | ||
397 | /** | ||
398 | * An InputStream which limits the number of bytes it reads at a time. We use this to make sure | ||
399 | * that CodedInputStream doesn't screw up when reading in small blocks. | ||
400 | @@ -659,6 +681,143 @@ public class CodedInputStreamTest { | ||
401 | } | ||
402 | } | ||
403 | |||
404 | + @Test | ||
405 | + public void testMaliciousRecursion_unknownFields() throws Exception { | ||
406 | + Throwable thrown = | ||
407 | + assertThrows( | ||
408 | + InvalidProtocolBufferException.class, | ||
409 | + () -> TestRecursiveMessage.parseFrom(NESTING_SGROUP)); | ||
410 | + | ||
411 | + assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); | ||
412 | + } | ||
413 | + | ||
414 | + @Test | ||
415 | + public void testMaliciousRecursion_skippingUnknownField() throws Exception { | ||
416 | + Throwable thrown = | ||
417 | + assertThrows( | ||
418 | + InvalidProtocolBufferException.class, | ||
419 | + () -> | ||
420 | + DiscardUnknownFieldsParser.wrap(TestRecursiveMessage.parser()) | ||
421 | + .parseFrom(NESTING_SGROUP)); | ||
422 | + | ||
423 | + assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); | ||
424 | + } | ||
425 | + | ||
426 | + @Test | ||
427 | + public void testMaliciousSGroupTagsWithMapField_fromInputStream() throws Exception { | ||
428 | + Throwable parseFromThrown = | ||
429 | + assertThrows( | ||
430 | + InvalidProtocolBufferException.class, | ||
431 | + () -> | ||
432 | + MapContainer.parseFrom( | ||
433 | + new ByteArrayInputStream(NESTING_SGROUP_WITH_INITIAL_BYTES))); | ||
434 | + Throwable mergeFromThrown = | ||
435 | + assertThrows( | ||
436 | + InvalidProtocolBufferException.class, | ||
437 | + () -> | ||
438 | + MapContainer.newBuilder() | ||
439 | + .mergeFrom(new ByteArrayInputStream(NESTING_SGROUP_WITH_INITIAL_BYTES))); | ||
440 | + | ||
441 | + assertThat(parseFromThrown) | ||
442 | + .hasMessageThat() | ||
443 | + .contains("Protocol message had too many levels of nesting"); | ||
444 | + assertThat(mergeFromThrown) | ||
445 | + .hasMessageThat() | ||
446 | + .contains("Protocol message had too many levels of nesting"); | ||
447 | + } | ||
448 | + | ||
449 | + @Test | ||
450 | + public void testMaliciousSGroupTags_inputStream_skipMessage() throws Exception { | ||
451 | + ByteArrayInputStream inputSteam = new ByteArrayInputStream(NESTING_SGROUP); | ||
452 | + CodedInputStream input = CodedInputStream.newInstance(inputSteam); | ||
453 | + CodedOutputStream output = CodedOutputStream.newInstance(new byte[NESTING_SGROUP.length]); | ||
454 | + | ||
455 | + Throwable thrown = assertThrows(InvalidProtocolBufferException.class, input::skipMessage); | ||
456 | + Throwable thrown2 = | ||
457 | + assertThrows(InvalidProtocolBufferException.class, () -> input.skipMessage(output)); | ||
458 | + | ||
459 | + assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); | ||
460 | + assertThat(thrown2) | ||
461 | + .hasMessageThat() | ||
462 | + .contains("Protocol message had too many levels of nesting"); | ||
463 | + } | ||
464 | + | ||
465 | + @Test | ||
466 | + public void testMaliciousSGroupTagsWithMapField_fromByteArray() throws Exception { | ||
467 | + Throwable parseFromThrown = | ||
468 | + assertThrows( | ||
469 | + InvalidProtocolBufferException.class, | ||
470 | + () -> MapContainer.parseFrom(NESTING_SGROUP_WITH_INITIAL_BYTES)); | ||
471 | + Throwable mergeFromThrown = | ||
472 | + assertThrows( | ||
473 | + InvalidProtocolBufferException.class, | ||
474 | + () -> MapContainer.newBuilder().mergeFrom(NESTING_SGROUP_WITH_INITIAL_BYTES)); | ||
475 | + | ||
476 | + assertThat(parseFromThrown) | ||
477 | + .hasMessageThat() | ||
478 | + .contains("the input ended unexpectedly in the middle of a field"); | ||
479 | + assertThat(mergeFromThrown) | ||
480 | + .hasMessageThat() | ||
481 | + .contains("the input ended unexpectedly in the middle of a field"); | ||
482 | + } | ||
483 | + | ||
484 | + @Test | ||
485 | + public void testMaliciousSGroupTags_arrayDecoder_skipMessage() throws Exception { | ||
486 | + CodedInputStream input = CodedInputStream.newInstance(NESTING_SGROUP); | ||
487 | + CodedOutputStream output = CodedOutputStream.newInstance(new byte[NESTING_SGROUP.length]); | ||
488 | + | ||
489 | + Throwable thrown = assertThrows(InvalidProtocolBufferException.class, input::skipMessage); | ||
490 | + Throwable thrown2 = | ||
491 | + assertThrows(InvalidProtocolBufferException.class, () -> input.skipMessage(output)); | ||
492 | + | ||
493 | + assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); | ||
494 | + assertThat(thrown2) | ||
495 | + .hasMessageThat() | ||
496 | + .contains("Protocol message had too many levels of nesting"); | ||
497 | + } | ||
498 | + | ||
499 | + @Test | ||
500 | + public void testMaliciousSGroupTagsWithMapField_fromByteBuffer() throws Exception { | ||
501 | + Throwable thrown = | ||
502 | + assertThrows( | ||
503 | + InvalidProtocolBufferException.class, | ||
504 | + () -> MapContainer.parseFrom(ByteBuffer.wrap(NESTING_SGROUP_WITH_INITIAL_BYTES))); | ||
505 | + | ||
506 | + assertThat(thrown) | ||
507 | + .hasMessageThat() | ||
508 | + .contains("the input ended unexpectedly in the middle of a field"); | ||
509 | + } | ||
510 | + | ||
511 | + @Test | ||
512 | + public void testMaliciousSGroupTags_byteBuffer_skipMessage() throws Exception { | ||
513 | + CodedInputStream input = InputType.NIO_DIRECT.newDecoder(NESTING_SGROUP); | ||
514 | + CodedOutputStream output = CodedOutputStream.newInstance(new byte[NESTING_SGROUP.length]); | ||
515 | + | ||
516 | + Throwable thrown = assertThrows(InvalidProtocolBufferException.class, input::skipMessage); | ||
517 | + Throwable thrown2 = | ||
518 | + assertThrows(InvalidProtocolBufferException.class, () -> input.skipMessage(output)); | ||
519 | + | ||
520 | + assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); | ||
521 | + assertThat(thrown2) | ||
522 | + .hasMessageThat() | ||
523 | + .contains("Protocol message had too many levels of nesting"); | ||
524 | + } | ||
525 | + | ||
526 | + @Test | ||
527 | + public void testMaliciousSGroupTags_iterableByteBuffer() throws Exception { | ||
528 | + CodedInputStream input = InputType.ITER_DIRECT.newDecoder(NESTING_SGROUP); | ||
529 | + CodedOutputStream output = CodedOutputStream.newInstance(new byte[NESTING_SGROUP.length]); | ||
530 | + | ||
531 | + Throwable thrown = assertThrows(InvalidProtocolBufferException.class, input::skipMessage); | ||
532 | + Throwable thrown2 = | ||
533 | + assertThrows(InvalidProtocolBufferException.class, () -> input.skipMessage(output)); | ||
534 | + | ||
535 | + assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); | ||
536 | + assertThat(thrown2) | ||
537 | + .hasMessageThat() | ||
538 | + .contains("Protocol message had too many levels of nesting"); | ||
539 | + } | ||
540 | + | ||
541 | private void checkSizeLimitExceeded(InvalidProtocolBufferException e) { | ||
542 | assertThat(e) | ||
543 | .hasMessageThat() | ||
544 | diff --git a/java/lite/src/test/java/com/google/protobuf/LiteTest.java b/java/lite/src/test/java/com/google/protobuf/LiteTest.java | ||
545 | index 754ed7d5f..81be90bfd 100644 | ||
546 | --- a/java/lite/src/test/java/com/google/protobuf/LiteTest.java | ||
547 | +++ b/java/lite/src/test/java/com/google/protobuf/LiteTest.java | ||
548 | @@ -2459,6 +2459,211 @@ public class LiteTest { | ||
549 | } | ||
550 | } | ||
551 | |||
552 | + @Test | ||
553 | + public void testParseFromInputStream_concurrent_nestingUnknownGroups() throws Exception { | ||
554 | + int numThreads = 200; | ||
555 | + ArrayList<Thread> threads = new ArrayList<>(); | ||
556 | + | ||
557 | + ByteString byteString = generateNestingGroups(99); | ||
558 | + AtomicBoolean thrown = new AtomicBoolean(false); | ||
559 | + | ||
560 | + for (int i = 0; i < numThreads; i++) { | ||
561 | + Thread thread = | ||
562 | + new Thread( | ||
563 | + () -> { | ||
564 | + try { | ||
565 | + TestAllTypesLite unused = TestAllTypesLite.parseFrom(byteString); | ||
566 | + } catch (IOException e) { | ||
567 | + if (e.getMessage().contains("Protocol message had too many levels of nesting")) { | ||
568 | + thrown.set(true); | ||
569 | + } | ||
570 | + } | ||
571 | + }); | ||
572 | + thread.start(); | ||
573 | + threads.add(thread); | ||
574 | + } | ||
575 | + | ||
576 | + for (Thread thread : threads) { | ||
577 | + thread.join(); | ||
578 | + } | ||
579 | + | ||
580 | + assertThat(thrown.get()).isFalse(); | ||
581 | + } | ||
582 | + | ||
583 | + @Test | ||
584 | + public void testParseFromInputStream_nestingUnknownGroups() throws IOException { | ||
585 | + ByteString byteString = generateNestingGroups(99); | ||
586 | + | ||
587 | + Throwable thrown = | ||
588 | + assertThrows( | ||
589 | + InvalidProtocolBufferException.class, () -> TestAllTypesLite.parseFrom(byteString)); | ||
590 | + assertThat(thrown) | ||
591 | + .hasMessageThat() | ||
592 | + .doesNotContain("Protocol message had too many levels of nesting"); | ||
593 | + } | ||
594 | + | ||
595 | + @Test | ||
596 | + public void testParseFromInputStream_nestingUnknownGroups_exception() throws IOException { | ||
597 | + ByteString byteString = generateNestingGroups(100); | ||
598 | + | ||
599 | + Throwable thrown = | ||
600 | + assertThrows( | ||
601 | + InvalidProtocolBufferException.class, () -> TestAllTypesLite.parseFrom(byteString)); | ||
602 | + assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); | ||
603 | + } | ||
604 | + | ||
605 | + @Test | ||
606 | + public void testParseFromInputStream_setRecursionLimit_exception() throws IOException { | ||
607 | + ByteString byteString = generateNestingGroups(199); | ||
608 | + UnknownFieldSchema<?, ?> schema = SchemaUtil.unknownFieldSetLiteSchema(); | ||
609 | + schema.setRecursionLimit(200); | ||
610 | + | ||
611 | + Throwable thrown = | ||
612 | + assertThrows( | ||
613 | + InvalidProtocolBufferException.class, () -> TestAllTypesLite.parseFrom(byteString)); | ||
614 | + assertThat(thrown) | ||
615 | + .hasMessageThat() | ||
616 | + .doesNotContain("Protocol message had too many levels of nesting"); | ||
617 | + schema.setRecursionLimit(UnknownFieldSchema.DEFAULT_RECURSION_LIMIT); | ||
618 | + } | ||
619 | + | ||
620 | + @Test | ||
621 | + public void testParseFromBytes_concurrent_nestingUnknownGroups() throws Exception { | ||
622 | + int numThreads = 200; | ||
623 | + ArrayList<Thread> threads = new ArrayList<>(); | ||
624 | + | ||
625 | + ByteString byteString = generateNestingGroups(99); | ||
626 | + AtomicBoolean thrown = new AtomicBoolean(false); | ||
627 | + | ||
628 | + for (int i = 0; i < numThreads; i++) { | ||
629 | + Thread thread = | ||
630 | + new Thread( | ||
631 | + () -> { | ||
632 | + try { | ||
633 | + // Should pass in byte[] instead of ByteString to go into ArrayDecoders. | ||
634 | + TestAllTypesLite unused = TestAllTypesLite.parseFrom(byteString.toByteArray()); | ||
635 | + } catch (InvalidProtocolBufferException e) { | ||
636 | + if (e.getMessage().contains("Protocol message had too many levels of nesting")) { | ||
637 | + thrown.set(true); | ||
638 | + } | ||
639 | + } | ||
640 | + }); | ||
641 | + thread.start(); | ||
642 | + threads.add(thread); | ||
643 | + } | ||
644 | + | ||
645 | + for (Thread thread : threads) { | ||
646 | + thread.join(); | ||
647 | + } | ||
648 | + | ||
649 | + assertThat(thrown.get()).isFalse(); | ||
650 | + } | ||
651 | + | ||
652 | + @Test | ||
653 | + public void testParseFromBytes_nestingUnknownGroups() throws IOException { | ||
654 | + ByteString byteString = generateNestingGroups(99); | ||
655 | + | ||
656 | + Throwable thrown = | ||
657 | + assertThrows( | ||
658 | + InvalidProtocolBufferException.class, | ||
659 | + () -> TestAllTypesLite.parseFrom(byteString.toByteArray())); | ||
660 | + assertThat(thrown) | ||
661 | + .hasMessageThat() | ||
662 | + .doesNotContain("Protocol message had too many levels of nesting"); | ||
663 | + } | ||
664 | + | ||
665 | + @Test | ||
666 | + public void testParseFromBytes_nestingUnknownGroups_exception() throws IOException { | ||
667 | + ByteString byteString = generateNestingGroups(100); | ||
668 | + | ||
669 | + Throwable thrown = | ||
670 | + assertThrows( | ||
671 | + InvalidProtocolBufferException.class, | ||
672 | + () -> TestAllTypesLite.parseFrom(byteString.toByteArray())); | ||
673 | + assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); | ||
674 | + } | ||
675 | + | ||
676 | + @Test | ||
677 | + public void testParseFromBytes_setRecursionLimit_exception() throws IOException { | ||
678 | + ByteString byteString = generateNestingGroups(199); | ||
679 | + ArrayDecoders.setRecursionLimit(200); | ||
680 | + | ||
681 | + Throwable thrown = | ||
682 | + assertThrows( | ||
683 | + InvalidProtocolBufferException.class, | ||
684 | + () -> TestAllTypesLite.parseFrom(byteString.toByteArray())); | ||
685 | + assertThat(thrown) | ||
686 | + .hasMessageThat() | ||
687 | + .doesNotContain("Protocol message had too many levels of nesting"); | ||
688 | + ArrayDecoders.setRecursionLimit(ArrayDecoders.DEFAULT_RECURSION_LIMIT); | ||
689 | + } | ||
690 | + | ||
691 | + @Test | ||
692 | + public void testParseFromBytes_recursiveMessages() throws Exception { | ||
693 | + byte[] data99 = makeRecursiveMessage(99).toByteArray(); | ||
694 | + byte[] data100 = makeRecursiveMessage(100).toByteArray(); | ||
695 | + | ||
696 | + RecursiveMessage unused = RecursiveMessage.parseFrom(data99); | ||
697 | + Throwable thrown = | ||
698 | + assertThrows( | ||
699 | + InvalidProtocolBufferException.class, () -> RecursiveMessage.parseFrom(data100)); | ||
700 | + assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); | ||
701 | + } | ||
702 | + | ||
703 | + @Test | ||
704 | + public void testParseFromBytes_recursiveKnownGroups() throws Exception { | ||
705 | + byte[] data99 = makeRecursiveGroup(99).toByteArray(); | ||
706 | + byte[] data100 = makeRecursiveGroup(100).toByteArray(); | ||
707 | + | ||
708 | + RecursiveGroup unused = RecursiveGroup.parseFrom(data99); | ||
709 | + Throwable thrown = | ||
710 | + assertThrows(InvalidProtocolBufferException.class, () -> RecursiveGroup.parseFrom(data100)); | ||
711 | + assertThat(thrown).hasMessageThat().contains("Protocol message had too many levels of nesting"); | ||
712 | + } | ||
713 | + | ||
714 | + @Test | ||
715 | + @SuppressWarnings("ProtoParseFromByteString") | ||
716 | + public void testMaliciousSGroupTagsWithMapField_fromByteArray() throws Exception { | ||
717 | + ByteString byteString = generateNestingGroups(102); | ||
718 | + | ||
719 | + Throwable parseFromThrown = | ||
720 | + assertThrows( | ||
721 | + InvalidProtocolBufferException.class, | ||
722 | + () -> MapContainer.parseFrom(byteString.toByteArray())); | ||
723 | + Throwable mergeFromThrown = | ||
724 | + assertThrows( | ||
725 | + InvalidProtocolBufferException.class, | ||
726 | + () -> MapContainer.newBuilder().mergeFrom(byteString.toByteArray())); | ||
727 | + | ||
728 | + assertThat(parseFromThrown) | ||
729 | + .hasMessageThat() | ||
730 | + .contains("Protocol message had too many levels of nesting"); | ||
731 | + assertThat(mergeFromThrown) | ||
732 | + .hasMessageThat() | ||
733 | + .contains("Protocol message had too many levels of nesting"); | ||
734 | + } | ||
735 | + | ||
736 | + @Test | ||
737 | + public void testMaliciousSGroupTagsWithMapField_fromInputStream() throws Exception { | ||
738 | + byte[] bytes = generateNestingGroups(101).toByteArray(); | ||
739 | + | ||
740 | + Throwable parseFromThrown = | ||
741 | + assertThrows( | ||
742 | + InvalidProtocolBufferException.class, | ||
743 | + () -> MapContainer.parseFrom(new ByteArrayInputStream(bytes))); | ||
744 | + Throwable mergeFromThrown = | ||
745 | + assertThrows( | ||
746 | + InvalidProtocolBufferException.class, | ||
747 | + () -> MapContainer.newBuilder().mergeFrom(new ByteArrayInputStream(bytes))); | ||
748 | + | ||
749 | + assertThat(parseFromThrown) | ||
750 | + .hasMessageThat() | ||
751 | + .contains("Protocol message had too many levels of nesting"); | ||
752 | + assertThat(mergeFromThrown) | ||
753 | + .hasMessageThat() | ||
754 | + .contains("Protocol message had too many levels of nesting"); | ||
755 | + } | ||
756 | + | ||
757 | @Test | ||
758 | public void testParseFromByteBuffer_extensions() throws Exception { | ||
759 | TestAllExtensionsLite message = | ||
760 | @@ -2815,4 +3020,31 @@ public class LiteTest { | ||
761 | } | ||
762 | return false; | ||
763 | } | ||
764 | + | ||
765 | + private static ByteString generateNestingGroups(int num) throws IOException { | ||
766 | + int groupTap = WireFormat.makeTag(3, WireFormat.WIRETYPE_START_GROUP); | ||
767 | + ByteString.Output byteStringOutput = ByteString.newOutput(); | ||
768 | + CodedOutputStream codedOutput = CodedOutputStream.newInstance(byteStringOutput); | ||
769 | + for (int i = 0; i < num; i++) { | ||
770 | + codedOutput.writeInt32NoTag(groupTap); | ||
771 | + } | ||
772 | + codedOutput.flush(); | ||
773 | + return byteStringOutput.toByteString(); | ||
774 | + } | ||
775 | + | ||
776 | + private static RecursiveMessage makeRecursiveMessage(int num) { | ||
777 | + if (num == 0) { | ||
778 | + return RecursiveMessage.getDefaultInstance(); | ||
779 | + } else { | ||
780 | + return RecursiveMessage.newBuilder().setRecurse(makeRecursiveMessage(num - 1)).build(); | ||
781 | + } | ||
782 | + } | ||
783 | + | ||
784 | + private static RecursiveGroup makeRecursiveGroup(int num) { | ||
785 | + if (num == 0) { | ||
786 | + return RecursiveGroup.getDefaultInstance(); | ||
787 | + } else { | ||
788 | + return RecursiveGroup.newBuilder().setRecurse(makeRecursiveGroup(num - 1)).build(); | ||
789 | + } | ||
790 | + } | ||
791 | } | ||
792 | -- | ||
793 | 2.25.1 | ||
794 | |||
diff --git a/meta-oe/recipes-devtools/protobuf/protobuf_4.25.3.bb b/meta-oe/recipes-devtools/protobuf/protobuf_4.25.8.bb index acc35db4a5..949a3b207b 100644 --- a/meta-oe/recipes-devtools/protobuf/protobuf_4.25.3.bb +++ b/meta-oe/recipes-devtools/protobuf/protobuf_4.25.8.bb | |||
@@ -10,12 +10,11 @@ LIC_FILES_CHKSUM = "file://LICENSE;md5=37b5762e07f0af8c74ce80a8bda4266b" | |||
10 | DEPENDS = "zlib abseil-cpp" | 10 | DEPENDS = "zlib abseil-cpp" |
11 | DEPENDS:append:class-target = " protobuf-native" | 11 | DEPENDS:append:class-target = " protobuf-native" |
12 | 12 | ||
13 | SRCREV = "4a2aef570deb2bfb8927426558701e8bfc26f2a4" | 13 | SRCREV = "a4cbdd3ed0042e8f9b9c30e8b0634096d9532809" |
14 | 14 | ||
15 | SRC_URI = "gitsm://github.com/protocolbuffers/protobuf.git;branch=25.x;protocol=https \ | 15 | SRC_URI = "gitsm://github.com/protocolbuffers/protobuf.git;branch=25.x;protocol=https \ |
16 | file://run-ptest \ | 16 | file://run-ptest \ |
17 | file://0001-examples-Makefile-respect-CXX-LDFLAGS-variables-fix-.patch \ | 17 | file://0001-examples-Makefile-respect-CXX-LDFLAGS-variables-fix-.patch \ |
18 | file://0001-Add-recursion-check-when-parsing-unknown-fields-in-J.patch \ | ||
19 | " | 18 | " |
20 | SRC_URI:append:mips:toolchain-clang = " file://0001-Fix-build-on-mips-clang.patch " | 19 | SRC_URI:append:mips:toolchain-clang = " file://0001-Fix-build-on-mips-clang.patch " |
21 | SRC_URI:append:mipsel:toolchain-clang = " file://0001-Fix-build-on-mips-clang.patch " | 20 | SRC_URI:append:mipsel:toolchain-clang = " file://0001-Fix-build-on-mips-clang.patch " |