Skip to content

Commit 05d3bc1

Browse files
authored
Merge pull request #90 from kares/expansion-limit-checks
prevent regexp expansion size overflow
2 parents 57fd57b + 33fc351 commit 05d3bc1

2 files changed

Lines changed: 27 additions & 4 deletions

File tree

‎src/org/joni/ArrayCompiler.java‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,12 @@ private void compileRangeRepeatNode(QuantifierNode qn, int targetLen, int emptyI
443443

444444
private static final int QUANTIFIER_EXPAND_LIMIT_SIZE = 50; // was 50
445445

446+
// MRI: regcomp.c IS_EXPAND_LIMIT_OK (since Ruby 4.0.8; 3.4.12 and 3.3.13)
447+
// prevent nested repeats from bypassing the limit and exhausting heap
448+
private static boolean isExpandLimitOK(int tlen, int n) {
449+
return n <= 0 || tlen <= QUANTIFIER_EXPAND_LIMIT_SIZE / n;
450+
}
451+
446452
private static boolean cknOn(int ckn) {
447453
return ckn > 0;
448454
}
@@ -660,7 +666,7 @@ private int compileNonCECLengthQuantifierNode(QuantifierNode qn) {
660666
}
661667

662668
int len;
663-
if (infinite && (qn.lower <= 1 || tlen * qn.lower <= QUANTIFIER_EXPAND_LIMIT_SIZE)) {
669+
if (infinite && (qn.lower <= 1 || isExpandLimitOK(tlen, qn.lower))) {
664670
if (qn.lower == 1 && tlen > QUANTIFIER_EXPAND_LIMIT_SIZE) {
665671
len = OPSize.JUMP;
666672
} else {
@@ -682,7 +688,7 @@ private int compileNonCECLengthQuantifierNode(QuantifierNode qn) {
682688
} else if (qn.upper == 0 && qn.isRefered) { /* /(?<n>..){0}/ */
683689
len = OPSize.JUMP + tlen;
684690
} else if (!infinite && qn.greedy &&
685-
(qn.upper == 1 || (tlen + OPSize.PUSH) * qn.upper <= QUANTIFIER_EXPAND_LIMIT_SIZE )) {
691+
(qn.upper == 1 || isExpandLimitOK(tlen + OPSize.PUSH, qn.upper))) {
686692
len = tlen * qn.lower;
687693
len += (OPSize.PUSH + tlen) * (qn.upper - qn.lower);
688694
} else if (!qn.greedy && qn.upper == 1 && qn.lower == 0) { /* '??' */
@@ -728,7 +734,7 @@ protected void compileNonCECQuantifierNode(QuantifierNode qn) {
728734
} else {
729735
modTLen = tlen;
730736
}
731-
if (infinite && (qn.lower <= 1 || tlen * qn.lower <= QUANTIFIER_EXPAND_LIMIT_SIZE)) {
737+
if (infinite && (qn.lower <= 1 || isExpandLimitOK(tlen, qn.lower))) {
732738
if (qn.lower == 1 && tlen > QUANTIFIER_EXPAND_LIMIT_SIZE) {
733739
if (qn.greedy) {
734740
if (qn.headExact != null) {
@@ -772,7 +778,7 @@ protected void compileNonCECQuantifierNode(QuantifierNode qn) {
772778
addOpcodeRelAddr(OPCode.JUMP, tlen);
773779
compileTree(qn.target);
774780
} else if (!infinite && qn.greedy &&
775-
(qn.upper == 1 || (tlen + OPSize.PUSH) * qn.upper <= QUANTIFIER_EXPAND_LIMIT_SIZE)) {
781+
(qn.upper == 1 || isExpandLimitOK(tlen + OPSize.PUSH, qn.upper))) {
776782
int n = qn.upper - qn.lower;
777783
compileTreeNTimes(qn.target, qn.lower);
778784

‎test/org/joni/test/TestCornerCases.java‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@
1919
*/
2020
package org.joni.test;
2121

22+
import static org.junit.Assert.assertEquals;
23+
24+
import java.nio.charset.StandardCharsets;
25+
2226
import org.jcodings.Encoding;
2327
import org.jcodings.specific.ASCIIEncoding;
2428
import org.joni.Config;
29+
import org.joni.Matcher;
2530
import org.joni.Option;
2631
import org.joni.Regex;
2732
import org.joni.Syntax;
@@ -56,4 +61,16 @@ public void test() throws Exception {
5661
nfail++;
5762
}
5863
}
64+
65+
@org.junit.Test(timeout = 30000)
66+
public void testNestedRepeatExpansionOverflow() {
67+
byte[] input = "xxxxxxxxxx".getBytes(StandardCharsets.US_ASCII);
68+
for (String source : new String[]{"(?:.{90000,}){90000}", "(?:.{46341,}){46341}",
69+
"(?:.{90000,}){90000,}", "(?:.{46341,}){46341,}"}) {
70+
byte[] pattern = source.getBytes(StandardCharsets.US_ASCII);
71+
Regex regex = new Regex(pattern, 0, pattern.length, Option.DEFAULT, ASCIIEncoding.INSTANCE, Syntax.DEFAULT);
72+
assertEquals(source, Matcher.FAILED, regex.matcher(input).search(0, input.length, Option.NONE));
73+
}
74+
}
75+
5976
}

0 commit comments

Comments
 (0)