diff --git a/logstash-core/build.gradle b/logstash-core/build.gradle index 30c64040c33..2b3bb6e6688 100644 --- a/logstash-core/build.gradle +++ b/logstash-core/build.gradle @@ -124,6 +124,8 @@ tasks.register("javaTests", Test) { exclude '/org/logstash/plugins/factory/PluginFactoryExtTest.class' exclude '/org/logstash/execution/ObservedExecutionTest.class' + maxHeapSize = "12g" + jacoco { enabled = true destinationFile = layout.buildDirectory.file('jacoco/test.exec').get().asFile diff --git a/logstash-core/src/main/java/org/logstash/common/BufferedTokenizerExt.java b/logstash-core/src/main/java/org/logstash/common/BufferedTokenizerExt.java index e2c476520c1..2161dc90049 100644 --- a/logstash-core/src/main/java/org/logstash/common/BufferedTokenizerExt.java +++ b/logstash-core/src/main/java/org/logstash/common/BufferedTokenizerExt.java @@ -48,7 +48,7 @@ public class BufferedTokenizerExt extends RubyObject { private RubyString delimiter = NEW_LINE; private int sizeLimit; private boolean hasSizeLimit; - private int inputSize; + private long inputSize; private boolean bufferFullErrorNotified = false; private String encodingName; diff --git a/logstash-core/src/test/java/org/logstash/common/BufferedTokenizerExtWithSizeLimitTest.java b/logstash-core/src/test/java/org/logstash/common/BufferedTokenizerExtWithSizeLimitTest.java index 9a07242369d..16181bb6f18 100644 --- a/logstash-core/src/test/java/org/logstash/common/BufferedTokenizerExtWithSizeLimitTest.java +++ b/logstash-core/src/test/java/org/logstash/common/BufferedTokenizerExtWithSizeLimitTest.java @@ -38,14 +38,19 @@ @SuppressWarnings("unchecked") public final class BufferedTokenizerExtWithSizeLimitTest extends RubyTestBase { + public static final int GB = 1024 * 1024 * 1024; private BufferedTokenizerExt sut; private ThreadContext context; @Before public void setUp() { + initSUTWithSizeLimit(10); + } + + private void initSUTWithSizeLimit(int sizeLimit) { sut = new BufferedTokenizerExt(RubyUtil.RUBY, RubyUtil.BUFFERED_TOKENIZER); context = RUBY.getCurrentContext(); - IRubyObject[] args = {RubyUtil.RUBY.newString("\n"), RubyUtil.RUBY.newFixnum(10)}; + IRubyObject[] args = {RubyUtil.RUBY.newString("\n"), RubyUtil.RUBY.newFixnum(sizeLimit)}; sut.init(context, args); } @@ -108,4 +113,29 @@ public void giveMultipleSegmentsThatGeneratesMultipleBufferFullErrorsThenIsAbleT RubyArray tokens = (RubyArray) sut.extract(context, RubyUtil.RUBY.newString("ccc\nddd\n")); assertEquals(List.of("ccccc", "ddd"), tokens); } + + @Test + public void givenMaliciousInputExtractDoesntOverflow() { + assertEquals("Xmx must equals to what's defined in the Gradle's javaTests task", + 12L * GB, Runtime.getRuntime().maxMemory()); + + // re-init the tokenizer with big sizeLimit + initSUTWithSizeLimit((int) (2L * GB) - 3); + // Integer.MAX_VALUE is 2 * GB + String bigFirstPiece = generateString("a", Integer.MAX_VALUE - 1024); + sut.extract(context, RubyUtil.RUBY.newString(bigFirstPiece)); + + // add another small fragment to trigger int overflow + // sizeLimit is (2^32-1)-3 first segment length is (2^32-1) - 1024 second is 1024 +2 + // so the combined length of first and second is > sizeLimit and should throw an expection + // but because of overflow it's negative and happens to be < sizeLimit + Exception thrownException = assertThrows(IllegalStateException.class, () -> { + sut.extract(context, RubyUtil.RUBY.newString(generateString("a", 1024 + 2))); + }); + assertThat(thrownException.getMessage(), containsString("input buffer full")); + } + + private String generateString(String fill, int size) { + return fill.repeat(size); + } } \ No newline at end of file