Author: fanningpj
Date: Sun Jun 21 12:55:20 2026
New Revision: 1935540
Log:
reject out-of-range quantifier counts in regex parser. Thanks to aizu-m. This
closes #64
Modified:
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/regex/RegexParser.java
xmlbeans/trunk/src/test/java/misc/checkin/RegularExpressionTest.java
Modified:
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/regex/RegexParser.java
==============================================================================
---
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/regex/RegexParser.java
Sun Jun 21 12:13:21 2026 (r1935539)
+++
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/regex/RegexParser.java
Sun Jun 21 12:55:20 2026 (r1935540)
@@ -73,7 +73,7 @@ class RegexParser {
int context = S_NORMAL;
int parennumber = 1;
boolean hasBackReferences;
- Vector references = null;
+ Vector<ReferencePosition> references = null;
public RegexParser() {
this.setLocale(Locale.getDefault());
@@ -112,7 +112,7 @@ class RegexParser {
throw ex("parser.parse.1", this.offset);
if (this.references != null) {
for (int i = 0; i < this.references.size(); i ++) {
- ReferencePosition position =
(ReferencePosition)this.references.elementAt(i);
+ ReferencePosition position = this.references.elementAt(i);
if (this.parennumber <= position.refNumber)
throw ex("parser.parse.2", position.position);
}
@@ -431,7 +431,7 @@ class RegexParser {
if ('1' <= ch && ch <= '9') {
refno = ch-'0';
this.hasBackReferences = true;
- if (this.references == null) this.references = new Vector();
+ if (this.references == null) this.references = new Vector<>();
this.references.addElement(new ReferencePosition(refno,
this.offset));
this.offset ++;
if (this.regex.charAt(this.offset) != ')') throw
ex("parser.factor.1", this.offset);
@@ -543,7 +543,7 @@ class RegexParser {
int refnum = this.chardata-'0';
Token tok = Token.createBackReference(refnum);
this.hasBackReferences = true;
- if (this.references == null) this.references = new Vector();
+ if (this.references == null) this.references = new Vector<>();
this.references.addElement(new ReferencePosition(refnum,
this.offset-2));
this.next();
return tok;
@@ -604,9 +604,9 @@ class RegexParser {
min = ch -'0';
while (off < this.regexlen
&& (ch = this.regex.charAt(off++)) >= '0' && ch <=
'9') {
- min = min*10 +ch-'0';
- if (min < 0)
+ if (min > (Integer.MAX_VALUE - (ch-'0')) / 10)
throw ex("parser.quantifier.5", this.offset);
+ min = min*10 +ch-'0';
}
}
else {
@@ -625,9 +625,9 @@ class RegexParser {
while (off < this.regexlen
&& (ch = this.regex.charAt(off++)) >= '0'
&& ch <= '9') {
- max = max*10 +ch-'0';
- if (max < 0)
+ if (max > (Integer.MAX_VALUE - (ch-'0')) / 10)
throw ex("parser.quantifier.5", this.offset);
+ max = max*10 +ch-'0';
}
if (min > max)
@@ -976,89 +976,92 @@ class RegexParser {
case 'r': c = '\r'; break; // CRRIAGE RETURN U+000D
case 't': c = '\t'; break; // HORIZONTAL TABULATION U+0009
//case 'v': c = 0x0b; break; // VERTICAL TABULATION U+000B
- case 'x':
- this.next();
- if (this.read() != T_CHAR) throw ex("parser.descape.1",
this.offset-1);
- if (this.chardata == '{') {
- int v1 = 0;
- int uv = 0;
- do {
- this.next();
- if (this.read() != T_CHAR) throw ex("parser.descape.1",
this.offset-1);
- if ((v1 = hexChar(this.chardata)) < 0)
- break;
- if (uv > uv*16) throw ex("parser.descape.2",
this.offset-1);
- uv = uv*16+v1;
- } while (true);
- if (this.chardata != '}') throw ex("parser.descape.3",
this.offset-1);
- if (uv > Token.UTF16_MAX) throw ex("parser.descape.4",
this.offset-1);
- c = uv;
- } else {
- int v1 = 0;
- if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
- throw ex("parser.descape.1", this.offset-1);
- int uv = v1;
- this.next();
- if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
- throw ex("parser.descape.1", this.offset-1);
- uv = uv*16+v1;
- c = uv;
- }
- break;
-
- case 'u':
- int v1 = 0;
- this.next();
- if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
- throw ex("parser.descape.1", this.offset-1);
- int uv = v1;
- this.next();
- if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
- throw ex("parser.descape.1", this.offset-1);
- uv = uv*16+v1;
- this.next();
- if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
- throw ex("parser.descape.1", this.offset-1);
- uv = uv*16+v1;
- this.next();
- if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
- throw ex("parser.descape.1", this.offset-1);
- uv = uv*16+v1;
- c = uv;
- break;
-
- case 'v':
- this.next();
- if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
- throw ex("parser.descape.1", this.offset-1);
- uv = v1;
- this.next();
- if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
- throw ex("parser.descape.1", this.offset-1);
- uv = uv*16+v1;
- this.next();
- if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
- throw ex("parser.descape.1", this.offset-1);
- uv = uv*16+v1;
- this.next();
- if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
- throw ex("parser.descape.1", this.offset-1);
- uv = uv*16+v1;
- this.next();
- if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
- throw ex("parser.descape.1", this.offset-1);
- uv = uv*16+v1;
- this.next();
- if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
- throw ex("parser.descape.1", this.offset-1);
- uv = uv*16+v1;
- if (uv > Token.UTF16_MAX) throw ex("parser.descappe.4",
this.offset-1);
- c = uv;
- break;
+ case 'x': {
+ this.next();
+ if (this.read() != T_CHAR) throw ex("parser.descape.1",
this.offset - 1);
+ if (this.chardata == '{') {
+ int v1 = 0;
+ int uv = 0;
+ do {
+ this.next();
+ if (this.read() != T_CHAR) throw ex("parser.descape.1",
this.offset - 1);
+ if ((v1 = hexChar(this.chardata)) < 0)
+ break;
+ if (uv > uv * 16) throw ex("parser.descape.2",
this.offset - 1);
+ uv = uv * 16 + v1;
+ } while (true);
+ if (this.chardata != '}') throw ex("parser.descape.3",
this.offset - 1);
+ if (uv > Token.UTF16_MAX) throw ex("parser.descape.4",
this.offset - 1);
+ c = uv;
+ } else {
+ int v1 = 0;
+ if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) <
0)
+ throw ex("parser.descape.1", this.offset - 1);
+ int uv = v1;
+ this.next();
+ if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) <
0)
+ throw ex("parser.descape.1", this.offset - 1);
+ uv = uv * 16 + v1;
+ c = uv;
+ }
+ break;
+ }
+ case 'u': {
+ int v1 = 0;
+ this.next();
+ if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
+ throw ex("parser.descape.1", this.offset - 1);
+ int uv1 = v1;
+ this.next();
+ if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
+ throw ex("parser.descape.1", this.offset - 1);
+ uv1 = uv1 * 16 + v1;
+ this.next();
+ if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
+ throw ex("parser.descape.1", this.offset - 1);
+ uv1 = uv1 * 16 + v1;
+ this.next();
+ if (this.read() != T_CHAR || (v1 = hexChar(this.chardata)) < 0)
+ throw ex("parser.descape.1", this.offset - 1);
+ uv1 = uv1 * 16 + v1;
+ c = uv1;
+ break;
+ }
+ case 'v': {
+ int v2 = 0;
+ int uv2 = 0;
+ this.next();
+ if (this.read() != T_CHAR || (v2 = hexChar(this.chardata)) < 0)
+ throw ex("parser.descape.1", this.offset - 1);
+ uv2 = v2;
+ this.next();
+ if (this.read() != T_CHAR || (v2 = hexChar(this.chardata)) < 0)
+ throw ex("parser.descape.1", this.offset - 1);
+ uv2 = uv2 * 16 + v2;
+ this.next();
+ if (this.read() != T_CHAR || (v2 = hexChar(this.chardata)) < 0)
+ throw ex("parser.descape.1", this.offset - 1);
+ uv2 = uv2 * 16 + v2;
+ this.next();
+ if (this.read() != T_CHAR || (v2 = hexChar(this.chardata)) < 0)
+ throw ex("parser.descape.1", this.offset - 1);
+ uv2 = uv2 * 16 + v2;
+ this.next();
+ if (this.read() != T_CHAR || (v2 = hexChar(this.chardata)) < 0)
+ throw ex("parser.descape.1", this.offset - 1);
+ uv2 = uv2 * 16 + v2;
+ this.next();
+ if (this.read() != T_CHAR || (v2 = hexChar(this.chardata)) < 0)
+ throw ex("parser.descape.1", this.offset - 1);
+ uv2 = uv2 * 16 + v2;
+ if (uv2 > Token.UTF16_MAX) throw ex("parser.descappe.4",
this.offset - 1);
+ c = uv2;
+ break;
+ }
case 'A':
case 'Z':
case 'z':
- throw ex("parser.descape.5", this.offset-2);
+ throw ex("parser.descape.5", this.offset-2);
default:
}
return c;
Modified: xmlbeans/trunk/src/test/java/misc/checkin/RegularExpressionTest.java
==============================================================================
--- xmlbeans/trunk/src/test/java/misc/checkin/RegularExpressionTest.java
Sun Jun 21 12:13:21 2026 (r1935539)
+++ xmlbeans/trunk/src/test/java/misc/checkin/RegularExpressionTest.java
Sun Jun 21 12:55:20 2026 (r1935540)
@@ -15,12 +15,14 @@
package misc.checkin;
+import org.apache.xmlbeans.impl.regex.ParseException;
import org.apache.xmlbeans.impl.regex.RegularExpression;
import org.junit.jupiter.api.Test;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RegularExpressionTest {
@@ -44,6 +46,23 @@ public class RegularExpressionTest {
assertFalse(new RegularExpression("x(?<=[a-c])").matches("xc"));
}
+ @Test
+ void testQuantifierOverflow() {
+ // a {min,max} count larger than Integer.MAX_VALUE overflowed the int
+ // accumulator. the only guard was a post-multiply min<0/max<0 check,
so
+ // counts that wrapped to a non-negative value slipped through:
"a{4294967296}"
+ // parsed as "a{0}" (matched the empty string) and "a{1,4294967298}"
as "a{1,2}",
+ // while bigger ones such as "a{99999999999}" blew the heap at match
time.
+ assertThrows(ParseException.class, () -> new
RegularExpression("a{4294967296}"));
+ assertThrows(ParseException.class, () -> new
RegularExpression("a{4294967297}"));
+ assertThrows(ParseException.class, () -> new
RegularExpression("a{99999999999}"));
+ assertThrows(ParseException.class, () -> new
RegularExpression("a{1,4294967298}"));
+ // counts up to Integer.MAX_VALUE are representable and must still
parse
+ new RegularExpression("a{2147483647}");
+ new RegularExpression("a{0,2147483647}");
+ assertTrue(new RegularExpression("a{2,4}").matches("aaa"));
+ }
+
private static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random rnd = new Random();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]