Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Check regex timeout in loops and repetitions
Browse files Browse the repository at this point in the history
Check the regex timeout in SetLoop and SetRepetition opcodes to avoid
the timeout not being handled.
  • Loading branch information
ViktorHofer committed May 31, 2019
1 parent 82408cd commit 98d4df2
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ internal abstract class RegexCompiler
private LocalBuilder _temp2V;
private LocalBuilder _temp3V;
private LocalBuilder _cultureV; // current culture is cached in local variable to prevent many thread local storage accesses for CultureInfo.CurrentCulture
private LocalBuilder _loopV; // counter for setrep and setloop

protected RegexCode _code; // the RegexCode object (used for debugging only)
protected int[] _codes; // the RegexCodes being translated
Expand Down Expand Up @@ -111,6 +112,7 @@ internal abstract class RegexCompiler
private const int Lazybranchcountback2 = 8; // back2 part of lazybranchcount
private const int Forejumpback = 9; // back part of forejump
private const int Uniquecount = 10;
private const int LoopTimeoutCheckCount = 2000; // A conservative value to guarantee the correct timeout handling.

static RegexCompiler()
{
Expand Down Expand Up @@ -368,6 +370,22 @@ private void Ret()
_ilg.Emit(OpCodes.Ret);
}

/*
* A macro for _ilg.Emit(OpCodes.Rem)
*/
private void Rem()
{
_ilg.Emit(OpCodes.Rem);
}

/*
* A macro for _ilg.Emit(OpCodes.Ceq)
*/
private void Ceq()
{
_ilg.Emit(OpCodes.Ceq);
}

/*
* A macro for _ilg.Emit(OpCodes.Pop)
*/
Expand Down Expand Up @@ -1602,6 +1620,7 @@ protected void GenerateGo()
_tempV = DeclareInt();
_temp2V = DeclareInt();
_temp3V = DeclareInt();
_loopV = DeclareInt();
_textbegV = DeclareInt();
_textendV = DeclareInt();
_textstartV = DeclareInt();
Expand Down Expand Up @@ -2787,6 +2806,7 @@ private void GenerateOneCode()

if (Code() == RegexCode.Setrep)
{
EmitTimeoutCheck();
Ldstr(_strings[Operand(0)]);
Call(s_charInSetM);

Expand Down Expand Up @@ -2896,6 +2916,7 @@ private void GenerateOneCode()

if (Code() == RegexCode.Setloop)
{
EmitTimeoutCheck();
Ldstr(_strings[Operand(0)]);
Call(s_charInSetM);

Expand Down Expand Up @@ -3105,5 +3126,28 @@ private void GenerateOneCode()
throw new NotImplementedException(SR.UnimplementedState);
}
}

private void EmitTimeoutCheck()
{
Label label = DefineLabel();

// Increment counter for each loop iteration.
Ldloc(_loopV);
Ldc(1);
Add();
Stloc(_loopV);

// Emit code to check the timeout every 2000th-iteration.
Ldloc(_loopV);
Ldc(LoopTimeoutCheckCount);
Rem();
Ldc(0);
Ceq();
Brfalse(label);
Ldthis();
Callvirt(s_checkTimeoutM);

MarkLabel(label);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal sealed class RegexInterpreter : RegexRunner
private int _codepos;
private bool _rightToLeft;
private bool _caseInsensitive;
private const int LoopTimeoutCheckCount = 2000; // A conservative value to guarantee the correct timeout handling.

public RegexInterpreter(RegexCode code, CultureInfo culture)
{
Expand Down Expand Up @@ -964,8 +965,18 @@ protected override void Go()
string set = _code.Strings[Operand(0)];

while (c-- > 0)
{
// Check the timeout every 2000th iteration. The aditional if check
// in every iteration can be neglected as the cost of the CharInClass
// check is many times higher.
if (c % LoopTimeoutCheckCount == 0)
{
CheckTimeout();
}

if (!RegexCharClass.CharInClass(Forwardcharnext(), set))
goto BreakBackward;
}

advance = 2;
continue;
Expand Down Expand Up @@ -1035,6 +1046,14 @@ protected override void Go()

for (i = c; i > 0; i--)
{
// Check the timeout every 2000th iteration. The aditional if check
// in every iteration can be neglected as the cost of the CharInClass
// check is many times higher.
if (i % LoopTimeoutCheckCount == 0)
{
CheckTimeout();
}

if (!RegexCharClass.CharInClass(Forwardcharnext(), set))
{
Backwardnext();
Expand Down
23 changes: 23 additions & 0 deletions src/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,29 @@ public void Match_Timeout_Throws()
}).Dispose();
}

[Theory]
[InlineData(RegexOptions.Compiled)]
[InlineData(RegexOptions.None)]
public void Match_Timeout_Loop_Throws(RegexOptions options)
{
var regex = new Regex(@"a\s+", options, TimeSpan.FromSeconds(1));
string input = @"a" + new string(' ', 800_000_000) + @"b";

Assert.Throws<RegexMatchTimeoutException>(() => regex.Match(input));
}

[Theory]
[InlineData(RegexOptions.Compiled)]
[InlineData(RegexOptions.None)]
public void Match_Timeout_Repetition_Throws(RegexOptions options)
{
int repetitionCount = 800_000_000;
var regex = new Regex(@"a\s{" + repetitionCount+ "}", options, TimeSpan.FromSeconds(1));
string input = @"a" + new string(' ', repetitionCount) + @"b";

Assert.Throws<RegexMatchTimeoutException>(() => regex.Match(input));
}

public static IEnumerable<object[]> Match_Advanced_TestData()
{
// \B special character escape: ".*\\B(SUCCESS)\\B.*"
Expand Down

0 comments on commit 98d4df2

Please sign in to comment.