Skip to content

Commit 1f661a2

Browse files
authored
Validate regex patterns against non-backtracking engine (#16687)
Resolves #16676 Resolves #16681 ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/Azure/bicep/pull/16687)
1 parent 6670980 commit 1f661a2

File tree

6 files changed

+78
-3
lines changed

6 files changed

+78
-3
lines changed

src/Bicep.Core.IntegrationTests/ScenarioTests.cs

+35
Original file line numberDiff line numberDiff line change
@@ -7080,4 +7080,39 @@ public void DependsOn_on_existing_resource_triggers_languageVersion_2()
70807080
result.Template.Should().HaveValueAtPath("$.languageVersion", "2.0");
70817081
result.Template.Should().HaveJsonAtPath("$.resources.sa.dependsOn", """["empty"]""");
70827082
}
7083+
7084+
[TestMethod]
7085+
public void Non_spec_compliant_provider_sourced_regexes_degrade_gracefully()
7086+
{
7087+
var result = CompilationHelper.Compile("""
7088+
param name string
7089+
param location string = resourceGroup().location
7090+
param sku object
7091+
param administratorLogin string
7092+
@secure()
7093+
param administratorLoginPassword string
7094+
param version string
7095+
7096+
resource mysqlServer 'Microsoft.DBforMySQL/flexibleServers@2023-06-30' = {
7097+
name: name
7098+
location: location
7099+
sku: sku
7100+
properties: {
7101+
version: version
7102+
administratorLogin: administratorLogin
7103+
administratorLoginPassword: administratorLoginPassword
7104+
}
7105+
7106+
resource firewall_all 'firewallRules' = {
7107+
name: 'allow-all-IPs'
7108+
properties: {
7109+
startIpAddress: '0.0.0.0'
7110+
endIpAddress: '255.255.255.255'
7111+
}
7112+
}
7113+
}
7114+
""");
7115+
7116+
result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
7117+
}
70837118
}

src/Bicep.Core/TypeSystem/Providers/Az/AzResourceTypeFactory.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ private TypeSymbol ToTypeSymbol(Azure.Bicep.Types.Concrete.TypeBase typeBase, bo
115115
case Azure.Bicep.Types.Concrete.StringType @string:
116116
return TypeFactory.CreateStringType(@string.MinLength,
117117
@string.MaxLength,
118-
@string.Pattern,
118+
TypeHelper.AsOptionalValidFiniteRegexPattern(@string.Pattern),
119119
GetValidationFlags(isResourceBodyType, isResourceBodyTopLevelPropertyType));
120120
case Azure.Bicep.Types.Concrete.BuiltInType builtInType:
121121
return builtInType.Kind switch

src/Bicep.Core/TypeSystem/Providers/MicrosoftGraph/MicrosoftGraphResourceTypeFactory.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ private TypeSymbol ToTypeSymbol(Azure.Bicep.Types.Concrete.TypeBase typeBase, bo
9191
return TypeFactory.CreateStringType(
9292
@string.MinLength,
9393
@string.MaxLength,
94-
@string.Pattern);
94+
TypeHelper.AsOptionalValidFiniteRegexPattern(@string.Pattern));
9595
case Azure.Bicep.Types.Concrete.BuiltInType builtInType:
9696
return builtInType.Kind switch
9797
{

src/Bicep.Core/TypeSystem/Providers/ThirdParty/ExtensibilityResourceTypeFactory.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ private TypeSymbol ToTypeSymbol(Azure.Bicep.Types.Concrete.TypeBase typeBase, bo
135135
return TypeFactory.CreateStringType(
136136
@string.MinLength,
137137
@string.MaxLength,
138-
@string.Pattern);
138+
TypeHelper.AsOptionalValidFiniteRegexPattern(@string.Pattern));
139139
case Azure.Bicep.Types.Concrete.BuiltInType builtInType:
140140
return builtInType.Kind switch
141141
{

src/Bicep.Core/TypeSystem/TypeHelper.cs

+35
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,41 @@ private static ObjectType RemovePropertyFlagsRecursively(
721721
property.Flags & ~flagsToRemove,
722722
property.Description));
723723

724+
/// <summary>
725+
/// Validates that the supplied pattern is: 1) a syntactically valid regular expression, and 2) compatible with
726+
/// .NET's non-backtracking regular expression engine.
727+
/// </summary>
728+
/// <param name="pattern">The regular expression pattern</param>
729+
/// <returns>The pattern string iff it can be used with the non-backtracking engine.</returns>
730+
public static string? AsOptionalValidFiniteRegexPattern(string? pattern)
731+
{
732+
if (pattern is not null && TryGetRegularExpressionValidationException(pattern) is null)
733+
{
734+
return pattern;
735+
}
736+
737+
return null;
738+
}
739+
740+
/// <summary>
741+
/// Attempts to instantiate a <see cref="Regex"/> with the supplied pattern and returns the error raised
742+
/// thereby.
743+
/// </summary>
744+
/// <param name="pattern">The regular expression pattern</param>
745+
/// <returns>The exception raised by <see cref="Regex.Regex(string, RegexOptions)"/>, if any.</returns>
746+
public static Exception? TryGetRegularExpressionValidationException(string pattern)
747+
{
748+
try
749+
{
750+
var _ = new Regex(pattern, RegexOptions.NonBacktracking);
751+
return null;
752+
}
753+
catch (Exception e)
754+
{
755+
return e;
756+
}
757+
}
758+
724759
public static bool MatchesPattern(string pattern, string value)
725760
=> Regex.IsMatch(value, pattern, RegexOptions.NonBacktracking);
726761
}

src/Bicep.Core/TypeSystem/Types/StringType.cs

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ public class StringType : TypeSymbol
88
internal StringType(long? minLength, long? maxLength, string? pattern, TypeSymbolValidationFlags validationFlags)
99
: base(LanguageConstants.TypeNameString)
1010
{
11+
if (pattern is not null && TypeHelper.TryGetRegularExpressionValidationException(pattern) is { } error)
12+
{
13+
throw new ArgumentException($"The supplied regular expression pattern /{pattern}/ is not valid", error);
14+
}
15+
1116
ValidationFlags = validationFlags;
1217
MinLength = minLength;
1318
MaxLength = maxLength;

0 commit comments

Comments
 (0)