Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions changelog.d/unreleased/2876.security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
category: security
issues:
- 2876
affected:
- src/CodeIndex/Indexer/BoundedRegex.cs
- src/CodeIndex/Indexer/Symbols
- tests/CodeIndex.Tests/SymbolExtractorTests.cs
---

## English

- **Bounded built-in symbol extraction regexes (#2876)** — built-in symbol extraction regular expressions now run with explicit match timeouts and fail closed on timeout instead of allowing hostile long lines to consume unbounded CPU.

## 日本語

- **組み込みシンボル抽出 regex に上限を設定しました (#2876)** — 組み込みシンボル抽出の正規表現に明示的な match timeout を設定し、悪意ある長い行が無制限に CPU を消費しないよう timeout 時は安全側で不一致として扱います。
16 changes: 16 additions & 0 deletions changelog.d/unreleased/2887.security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
category: security
issues:
- 2887
affected:
- src/CodeIndex/Indexer/References
- tests/CodeIndex.Tests/ReferenceExtractorTests.cs
---

## English

- **Bounded reference extraction regexes (#2887)** — reference extraction regular expressions, including static replace calls, now run through bounded match timeouts and fail closed on timeout for hostile long-line inputs.

## 日本語

- **参照抽出 regex に上限を設定しました (#2887)** — 静的な replace 呼び出しを含む参照抽出の正規表現を bounded match timeout 経由にし、悪意ある長い行で timeout した場合は安全側で処理を打ち切ります。
221 changes: 221 additions & 0 deletions src/CodeIndex/Indexer/BoundedRegex.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
using System.Text.RegularExpressions;
using BclMatch = System.Text.RegularExpressions.Match;
using BclRegex = System.Text.RegularExpressions.Regex;

namespace CodeIndex.Indexer;

internal sealed class BoundedRegex : BclRegex
{
internal static readonly TimeSpan DefaultMatchTimeout = TimeSpan.FromMilliseconds(250);

public BoundedRegex(string pattern)
: base(pattern, RegexOptions.None, DefaultMatchTimeout)
{
}

public BoundedRegex(string pattern, RegexOptions options)
: base(pattern, options, DefaultMatchTimeout)
{
}

public BoundedRegex(string pattern, RegexOptions options, TimeSpan matchTimeout)
: base(pattern, options, matchTimeout)
{
}

public static new string Escape(string str) => BclRegex.Escape(str);

public static new string Unescape(string str) => BclRegex.Unescape(str);

public static new Match Match(string input, string pattern) =>
Match(input, pattern, RegexOptions.None);

public static new Match Match(string input, string pattern, RegexOptions options)
{
try
{
return BclRegex.Match(input, pattern, options, DefaultMatchTimeout);
}
catch (RegexMatchTimeoutException)
{
return BclMatch.Empty;
}
}

public static new MatchCollection Matches(string input, string pattern) =>
Matches(input, pattern, RegexOptions.None);

public static new MatchCollection Matches(string input, string pattern, RegexOptions options)
{
try
{
var matches = BclRegex.Matches(input, pattern, options, DefaultMatchTimeout);
_ = matches.Count;
return matches;
}
catch (RegexMatchTimeoutException)
{
return EmptyMatches();
}
}

public static new bool IsMatch(string input, string pattern) =>
IsMatch(input, pattern, RegexOptions.None);

public static new bool IsMatch(string input, string pattern, RegexOptions options)
{
try
{
return BclRegex.IsMatch(input, pattern, options, DefaultMatchTimeout);
}
catch (RegexMatchTimeoutException)
{
return false;
}
}

public static new string Replace(string input, string pattern, string replacement) =>
Replace(input, pattern, replacement, RegexOptions.None);

public static new string Replace(string input, string pattern, string replacement, RegexOptions options)
{
try
{
return BclRegex.Replace(input, pattern, replacement, options, DefaultMatchTimeout);
}
catch (RegexMatchTimeoutException)
{
return input;
}
}

public static new string Replace(string input, string pattern, MatchEvaluator evaluator) =>
Replace(input, pattern, evaluator, RegexOptions.None);

public static new string Replace(string input, string pattern, MatchEvaluator evaluator, RegexOptions options)
{
try
{
return BclRegex.Replace(input, pattern, evaluator, options, DefaultMatchTimeout);
}
catch (RegexMatchTimeoutException)
{
return input;
}
}

public new Match Match(string input)
{
try
{
return base.Match(input);
}
catch (RegexMatchTimeoutException)
{
return BclMatch.Empty;
}
}

public new Match Match(string input, int startat)
{
try
{
return base.Match(input, startat);
}
catch (RegexMatchTimeoutException)
{
return BclMatch.Empty;
}
}

public new Match Match(string input, int beginning, int length)
{
try
{
return base.Match(input, beginning, length);
}
catch (RegexMatchTimeoutException)
{
return BclMatch.Empty;
}
}

public new MatchCollection Matches(string input)
{
try
{
var matches = base.Matches(input);
_ = matches.Count;
return matches;
}
catch (RegexMatchTimeoutException)
{
return EmptyMatches();
}
}

public new MatchCollection Matches(string input, int startat)
{
try
{
var matches = base.Matches(input, startat);
_ = matches.Count;
return matches;
}
catch (RegexMatchTimeoutException)
{
return EmptyMatches();
}
}

public new bool IsMatch(string input)
{
try
{
return base.IsMatch(input);
}
catch (RegexMatchTimeoutException)
{
return false;
}
}

public new bool IsMatch(string input, int startat)
{
try
{
return base.IsMatch(input, startat);
}
catch (RegexMatchTimeoutException)
{
return false;
}
}

public new string Replace(string input, string replacement)
{
try
{
return base.Replace(input, replacement);
}
catch (RegexMatchTimeoutException)
{
return input;
}
}

public new string Replace(string input, MatchEvaluator evaluator)
{
try
{
return base.Replace(input, evaluator);
}
catch (RegexMatchTimeoutException)
{
return input;
}
}

private static MatchCollection EmptyMatches() =>
BclRegex.Matches(string.Empty, @"\b\B", RegexOptions.None, DefaultMatchTimeout);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;
using CodeIndex.Models;

namespace CodeIndex.Indexer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;
using CodeIndex.Models;

namespace CodeIndex.Indexer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text;
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;
using CodeIndex.Models;

namespace CodeIndex.Indexer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text;
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;
using CodeIndex.Models;

namespace CodeIndex.Indexer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;
using CodeIndex.Models;
using CSharpContainingTypeValueReceiverNames = CodeIndex.Indexer.ReferenceExtractor.CSharpContainingTypeValueReceiverNames;
using CSharpFunctionValueReceiverNameRecord = CodeIndex.Indexer.ReferenceExtractor.CSharpFunctionValueReceiverNameRecord;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;
using CodeIndex.Models;

namespace CodeIndex.Indexer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CodeIndex.Models;
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;

namespace CodeIndex.Indexer;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;
using CodeIndex.Models;

namespace CodeIndex.Indexer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;
using CodeIndex.Models;

namespace CodeIndex.Indexer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;
using CodeIndex.Models;

namespace CodeIndex.Indexer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CodeIndex.Models;
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;

namespace CodeIndex.Indexer;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;

namespace CodeIndex.Indexer;

Expand Down
20 changes: 11 additions & 9 deletions src/CodeIndex/Indexer/References/Languages/GoReferenceExtractor.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;
using CodeIndex.Models;

namespace CodeIndex.Indexer;

internal static class GoReferenceExtractor
{
private static readonly System.Text.RegularExpressions.Regex GoroutineCallRegex = new(
private static readonly Regex GoroutineCallRegex = new(
@"\bgo\s+(?<name>[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\s*\(",
System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.CultureInvariant);
RegexOptions.Compiled | RegexOptions.CultureInvariant);

private static readonly System.Text.RegularExpressions.Regex ChannelSendRegex = new(
private static readonly Regex ChannelSendRegex = new(
@"(?<!<)(?<name>[A-Za-z_]\w*)\s*<-",
System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.CultureInvariant);
RegexOptions.Compiled | RegexOptions.CultureInvariant);

private static readonly System.Text.RegularExpressions.Regex ChannelReceiveRegex = new(
private static readonly Regex ChannelReceiveRegex = new(
@"(?<!<)<-\s*(?<name>[A-Za-z_]\w*)",
System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.CultureInvariant);
RegexOptions.Compiled | RegexOptions.CultureInvariant);

public static bool[] BuildImportBlockLineMap(IReadOnlyList<string> originalLines)
=> LanguageReferenceExtractionSupport.BuildGoImportBlockLineMap(originalLines);
Expand All @@ -28,7 +30,7 @@ public static void EmitConcurrencyReferences(
int lineNumber,
Func<int, SymbolRecord?> resolveContainerForColumn)
{
foreach (System.Text.RegularExpressions.Match match in GoroutineCallRegex.Matches(preparedLine))
foreach (Match match in GoroutineCallRegex.Matches(preparedLine))
{
var group = match.Groups["name"];
var rawName = group.Value;
Expand All @@ -47,7 +49,7 @@ public static void EmitConcurrencyReferences(
resolveContainerForColumn(nameIndex));
}

foreach (System.Text.RegularExpressions.Match match in ChannelSendRegex.Matches(preparedLine))
foreach (Match match in ChannelSendRegex.Matches(preparedLine))
{
ReferenceExtractor.AddReference(
references,
Expand All @@ -61,7 +63,7 @@ public static void EmitConcurrencyReferences(
resolveContainerForColumn(match.Groups["name"].Index));
}

foreach (System.Text.RegularExpressions.Match match in ChannelReceiveRegex.Matches(preparedLine))
foreach (Match match in ChannelReceiveRegex.Matches(preparedLine))
{
if (!IsGoChannelReceiveArrow(preparedLine, match.Index))
continue;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using Regex = CodeIndex.Indexer.BoundedRegex;

namespace CodeIndex.Indexer;

Expand Down
Loading
Loading