Skip to content

Commit 825185b

Browse files
Harden beats packet parsing (#26139) (#26165)
* add hard upper bounds to declared frame sizes when parsing beats packets * add changelog (cherry picked from commit 562b88a) Co-authored-by: Kay Roepke <kroepke@googlemail.com>
1 parent 5e6d33d commit 825185b

3 files changed

Lines changed: 62 additions & 1 deletion

File tree

changelog/unreleased/pr-26139.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
type = "f"
2+
message = "Add safeguards for beats protocol frame parsing."
3+
4+
pulls = ["26139"]

graylog2-server/src/main/java/org/graylog/plugins/beats/BeatsFrameDecoder.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ enum DecodingState {
6464
FRAME_WINDOW_SIZE
6565
}
6666

67+
private static final int MAX_JSON_FRAME_BYTES = 16 * 1024 * 1024;
68+
private static final int MAX_DATA_PAIRS = 1024;
69+
private static final int MAX_DATA_ITEM_BYTES = 1024 * 1024;
70+
6771
private long windowSize;
6872
private long sequenceNum;
6973

@@ -165,6 +169,9 @@ private Collection<ByteBuf> parseJsonFrame(Channel channel, ByteBuf channelBuffe
165169
LOG.trace("Received sequence number {}", sequenceNum);
166170

167171
final int jsonLength = Ints.saturatedCast(channelBuffer.readUnsignedInt());
172+
if (jsonLength > MAX_JSON_FRAME_BYTES) {
173+
throw new IllegalStateException("JSON frame size " + jsonLength + " exceeds maximum " + MAX_JSON_FRAME_BYTES);
174+
}
168175

169176
final ByteBuf buffer = channelBuffer.readBytes(jsonLength);
170177
sendACK(channel);
@@ -211,6 +218,9 @@ private Collection<ByteBuf> parseDataFrame(Channel channel, ByteBuf channelBuffe
211218
LOG.trace("Received sequence number {}", sequenceNum);
212219

213220
final int pairs = Ints.saturatedCast(channelBuffer.readUnsignedInt());
221+
if (pairs > MAX_DATA_PAIRS) {
222+
throw new IllegalStateException("Data frame pair count " + pairs + " exceeds maximum " + MAX_DATA_PAIRS);
223+
}
214224
final JsonFactory jsonFactory = new JsonFactory();
215225
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
216226
try (final JsonGenerator jg = jsonFactory.createGenerator(outputStream)) {
@@ -231,6 +241,9 @@ private Collection<ByteBuf> parseDataFrame(Channel channel, ByteBuf channelBuffe
231241

232242
private String parseDataItem(ByteBuf buf) {
233243
int length = Ints.saturatedCast(buf.readUnsignedInt());
244+
if (length > MAX_DATA_ITEM_BYTES) {
245+
throw new IllegalStateException("Data item size " + length + " exceeds maximum " + MAX_DATA_ITEM_BYTES);
246+
}
234247
final ByteBuf item = buf.readSlice(length);
235248
return item.toString(StandardCharsets.UTF_8);
236249
}

graylog2-server/src/test/java/org/graylog/plugins/beats/BeatsFrameDecoderTest.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.netty.buffer.ByteBuf;
2222
import io.netty.buffer.Unpooled;
2323
import io.netty.channel.embedded.EmbeddedChannel;
24+
import io.netty.handler.codec.DecoderException;
2425
import io.netty.handler.logging.LoggingHandler;
2526
import org.graylog2.jackson.TypeReferences;
2627
import org.graylog2.shared.bindings.providers.ObjectMapperProvider;
@@ -32,6 +33,7 @@
3233
import java.util.zip.Deflater;
3334

3435
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
3537

3638
public class BeatsFrameDecoderTest {
3739
private final ObjectMapper objectMapper = new ObjectMapperProvider().get();
@@ -238,4 +240,46 @@ private long extractSequenceNumber(ByteBuf buffer) {
238240
assertThat(buffer.readableBytes()).isEqualTo(0);
239241
return seqNum;
240242
}
241-
}
243+
244+
@Test
245+
public void rejectsJsonFrameExceedingLengthLimit() {
246+
final byte[] oversizedJson = ("{\"message\":\"" + "X".repeat(16 * 1024 * 1024) + "\"}")
247+
.getBytes(StandardCharsets.UTF_8);
248+
249+
final ByteBuf frame = buildJsonFrame(oversizedJson, 0);
250+
251+
assertThatThrownBy(() -> {
252+
channel.writeInbound(frame);
253+
channel.checkException();
254+
}).isInstanceOf(DecoderException.class);
255+
}
256+
257+
@Test
258+
public void rejectsDataFrameExceedingPairLimit() {
259+
final ByteBuf frame = Unpooled.buffer();
260+
frame.writeByte('2');
261+
frame.writeByte('D');
262+
frame.writeInt(0);
263+
frame.writeInt(1025);
264+
265+
assertThatThrownBy(() -> {
266+
channel.writeInbound(frame);
267+
channel.checkException();
268+
}).isInstanceOf(DecoderException.class);
269+
}
270+
271+
@Test
272+
public void rejectsDataItemExceedingLengthLimit() {
273+
final ByteBuf frame = Unpooled.buffer();
274+
frame.writeByte('2');
275+
frame.writeByte('D');
276+
frame.writeInt(0);
277+
frame.writeInt(1);
278+
frame.writeInt(1024 * 1024 + 1);
279+
280+
assertThatThrownBy(() -> {
281+
channel.writeInbound(frame);
282+
channel.checkException();
283+
}).isInstanceOf(DecoderException.class);
284+
}
285+
}

0 commit comments

Comments
 (0)