未验证 提交 22ba36d5 编写于 作者: D Drew Kersnar 提交者: GitHub

Fix overflow check for parsing format strings (#72647)

* WIP: fix overflow checking

* Change limit to 9 digits

* Fixed the same bug in other places

* Remove mistake include

* Move expensive tests to OuterLoop
上级 1dc5eba5
...@@ -700,19 +700,19 @@ internal static char ParseFormatSpecifier(ReadOnlySpan<char> format, out int dig ...@@ -700,19 +700,19 @@ internal static char ParseFormatSpecifier(ReadOnlySpan<char> format, out int dig
} }
} }
// Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99, // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 999_999_999,
// but it can begin with any number of 0s, and thus we may need to check more than two // but it can begin with any number of 0s, and thus we may need to check more than 9
// digits. Further, for compat, we need to stop when we hit a null char. // digits. Further, for compat, we need to stop when we hit a null char.
int n = 0; int n = 0;
int i = 1; int i = 1;
while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i])) while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i]))
{ {
int temp = (n * 10) + format[i++] - '0'; // Check if we are about to overflow past our limit of 9 digits
if (temp < n) if (n >= 100_000_000)
{ {
throw new FormatException(SR.Argument_BadFormatSpecifier); throw new FormatException(SR.Argument_BadFormatSpecifier);
} }
n = temp; n = ((n * 10) + format[i++] - '0');
} }
// If we're at the end of the digits rather than having stopped because we hit something // If we're at the end of the digits rather than having stopped because we hit something
......
...@@ -2318,19 +2318,19 @@ internal static unsafe char ParseFormatSpecifier(ReadOnlySpan<char> format, out ...@@ -2318,19 +2318,19 @@ internal static unsafe char ParseFormatSpecifier(ReadOnlySpan<char> format, out
} }
} }
// Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99, // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 999_999_999,
// but it can begin with any number of 0s, and thus we may need to check more than two // but it can begin with any number of 0s, and thus we may need to check more than 9
// digits. Further, for compat, we need to stop when we hit a null char. // digits. Further, for compat, we need to stop when we hit a null char.
int n = 0; int n = 0;
int i = 1; int i = 1;
while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i])) while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i]))
{ {
int temp = ((n * 10) + format[i++] - '0'); // Check if we are about to overflow past our limit of 9 digits
if (temp < n) if (n >= 100_000_000)
{ {
throw new FormatException(SR.Argument_BadFormatSpecifier); throw new FormatException(SR.Argument_BadFormatSpecifier);
} }
n = temp; n = ((n * 10) + format[i++] - '0');
} }
// If we're at the end of the digits rather than having stopped because we hit something // If we're at the end of the digits rather than having stopped because we hit something
......
...@@ -854,7 +854,6 @@ void MultiplyAdd(ref Span<uint> currentBuffer, uint multiplier, uint addValue) ...@@ -854,7 +854,6 @@ void MultiplyAdd(ref Span<uint> currentBuffer, uint multiplier, uint addValue)
} }
} }
// This function is consistent with VM\COMNumber.cpp!COMNumber::ParseFormatSpecifier
internal static char ParseFormatSpecifier(ReadOnlySpan<char> format, out int digits) internal static char ParseFormatSpecifier(ReadOnlySpan<char> format, out int digits)
{ {
digits = -1; digits = -1;
...@@ -867,22 +866,23 @@ internal static char ParseFormatSpecifier(ReadOnlySpan<char> format, out int dig ...@@ -867,22 +866,23 @@ internal static char ParseFormatSpecifier(ReadOnlySpan<char> format, out int dig
char ch = format[i]; char ch = format[i];
if (char.IsAsciiLetter(ch)) if (char.IsAsciiLetter(ch))
{ {
// The digits value must be >= 0 && <= 999_999_999,
// but it can begin with any number of 0s, and thus we may need to check more than 9
// digits. Further, for compat, we need to stop when we hit a null char.
i++; i++;
int n = -1; int n = 0;
while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i]))
if (i < format.Length && char.IsAsciiDigit(format[i]))
{ {
n = format[i++] - '0'; // Check if we are about to overflow past our limit of 9 digits
while (i < format.Length && char.IsAsciiDigit(format[i])) if (n >= 100_000_000)
{ {
int temp = n * 10 + (format[i++] - '0'); throw new FormatException(SR.Argument_BadFormatSpecifier);
if (temp < n)
{
throw new FormatException(SR.Argument_BadFormatSpecifier);
}
n = temp;
} }
n = ((n * 10) + format[i++] - '0');
} }
// If we're at the end of the digits rather than having stopped because we hit something
// other than a digit or overflowed, return the standard format info.
if (i >= format.Length || format[i] == '\0') if (i >= format.Length || format[i] == '\0')
{ {
digits = n; digits = n;
......
...@@ -439,6 +439,46 @@ public static void CustomFormatPerMille() ...@@ -439,6 +439,46 @@ public static void CustomFormatPerMille()
RunCustomFormatToStringTests(s_random, "#\u2030000000", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 6, PerMilleSymbolFormatter); RunCustomFormatToStringTests(s_random, "#\u2030000000", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 6, PerMilleSymbolFormatter);
} }
[Fact]
public static void ToString_InvalidFormat_ThrowsFormatException()
{
BigInteger b = new BigInteger(123456789000m);
// Format precision limit is 999_999_999 (9 digits). Anything larger should throw.
// Check ParseFormatSpecifier in FormatProvider.Number.cs with `E` format
Assert.Throws<FormatException>(() => b.ToString("E" + int.MaxValue.ToString()));
long intMaxPlus1 = (long)int.MaxValue + 1;
string intMaxPlus1String = intMaxPlus1.ToString();
Assert.Throws<FormatException>(() => b.ToString("E" + intMaxPlus1String));
Assert.Throws<FormatException>(() => b.ToString("E4772185890"));
Assert.Throws<FormatException>(() => b.ToString("E1000000000"));
Assert.Throws<FormatException>(() => b.ToString("E000001000000000"));
// Check ParseFormatSpecifier in BigNumber.cs with `G` format
Assert.Throws<FormatException>(() => b.ToString("G" + int.MaxValue.ToString()));
Assert.Throws<FormatException>(() => b.ToString("G" + intMaxPlus1String));
Assert.Throws<FormatException>(() => b.ToString("G4772185890"));
Assert.Throws<FormatException>(() => b.ToString("G1000000000"));
Assert.Throws<FormatException>(() => b.ToString("G000001000000000"));
}
[Fact]
[OuterLoop("Takes a long time, allocates a lot of memory")]
public static void ToString_ValidLargeFormat()
{
BigInteger b = new BigInteger(123456789000m);
// Format precision limit is 999_999_999 (9 digits). Anything larger should throw.
// Check ParseFormatSpecifier in FormatProvider.Number.cs with `E` format
b.ToString("E999999999"); // Should not throw
b.ToString("E00000999999999"); // Should not throw
// Check ParseFormatSpecifier in BigNumber.cs with `G` format
b.ToString("G999999999"); // Should not throw
b.ToString("G00000999999999"); // Should not throw
}
private static void RunSimpleProviderToStringTests(Random random, string format, NumberFormatInfo provider, int precision, StringFormatter formatter) private static void RunSimpleProviderToStringTests(Random random, string format, NumberFormatInfo provider, int precision, StringFormatter formatter)
{ {
string test; string test;
......
...@@ -827,9 +827,27 @@ public static void ToString_InvalidFormat_ThrowsFormatException() ...@@ -827,9 +827,27 @@ public static void ToString_InvalidFormat_ThrowsFormatException()
double d = 123.0; double d = 123.0;
Assert.Throws<FormatException>(() => d.ToString("Y")); // Invalid format Assert.Throws<FormatException>(() => d.ToString("Y")); // Invalid format
Assert.Throws<FormatException>(() => d.ToString("Y", null)); // Invalid format Assert.Throws<FormatException>(() => d.ToString("Y", null)); // Invalid format
// Format precision limit is 999_999_999 (9 digits). Anything larger should throw.
Assert.Throws<FormatException>(() => d.ToString("E" + int.MaxValue.ToString()));
long intMaxPlus1 = (long)int.MaxValue + 1; long intMaxPlus1 = (long)int.MaxValue + 1;
string intMaxPlus1String = intMaxPlus1.ToString(); string intMaxPlus1String = intMaxPlus1.ToString();
Assert.Throws<FormatException>(() => d.ToString("E" + intMaxPlus1String)); Assert.Throws<FormatException>(() => d.ToString("E" + intMaxPlus1String));
Assert.Throws<FormatException>(() => d.ToString("E4772185890"));
Assert.Throws<FormatException>(() => d.ToString("E1000000000"));
Assert.Throws<FormatException>(() => d.ToString("E000001000000000"));
}
[Fact]
[OuterLoop("Takes a long time, allocates a lot of memory")]
public static void ToString_ValidLargeFormat()
{
double d = 123.0;
// Format precision limit is 999_999_999 (9 digits). Anything larger should throw.
d.ToString("E999999999"); // Should not throw
d.ToString("E00000999999999"); // Should not throw
} }
[Theory] [Theory]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册