未验证 提交 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
}
}
// Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99,
// but it can begin with any number of 0s, and thus we may need to check more than two
// 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 9
// digits. Further, for compat, we need to stop when we hit a null char.
int n = 0;
int i = 1;
while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i]))
{
int temp = (n * 10) + format[i++] - '0';
if (temp < n)
// Check if we are about to overflow past our limit of 9 digits
if (n >= 100_000_000)
{
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
......
......@@ -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,
// but it can begin with any number of 0s, and thus we may need to check more than two
// 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 9
// digits. Further, for compat, we need to stop when we hit a null char.
int n = 0;
int i = 1;
while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i]))
{
int temp = ((n * 10) + format[i++] - '0');
if (temp < n)
// Check if we are about to overflow past our limit of 9 digits
if (n >= 100_000_000)
{
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
......
......@@ -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)
{
digits = -1;
......@@ -867,22 +866,23 @@ internal static char ParseFormatSpecifier(ReadOnlySpan<char> format, out int dig
char ch = format[i];
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++;
int n = -1;
if (i < format.Length && char.IsAsciiDigit(format[i]))
int n = 0;
while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i]))
{
n = format[i++] - '0';
while (i < format.Length && char.IsAsciiDigit(format[i]))
// Check if we are about to overflow past our limit of 9 digits
if (n >= 100_000_000)
{
int temp = n * 10 + (format[i++] - '0');
if (temp < n)
{
throw new FormatException(SR.Argument_BadFormatSpecifier);
}
n = temp;
throw new FormatException(SR.Argument_BadFormatSpecifier);
}
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')
{
digits = n;
......
......@@ -439,6 +439,46 @@ public static void CustomFormatPerMille()
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)
{
string test;
......
......@@ -827,9 +827,27 @@ public static void ToString_InvalidFormat_ThrowsFormatException()
double d = 123.0;
Assert.Throws<FormatException>(() => d.ToString("Y")); // 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;
string intMaxPlus1String = intMaxPlus1.ToString();
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]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册