diff --git a/Jint.Tests/Runtime/BigIntTests.cs b/Jint.Tests/Runtime/BigIntTests.cs index c3ef814d8f..5ee2237571 100644 --- a/Jint.Tests/Runtime/BigIntTests.cs +++ b/Jint.Tests/Runtime/BigIntTests.cs @@ -1,5 +1,6 @@ using System.Numerics; using Jint.Native; +using Jint.Runtime; namespace Jint.Tests.Runtime; @@ -67,4 +68,60 @@ public void ObjectIsReturnsFalseDifferentComparisonsWithBigInt(string statement) // Assert Assert.False(result.AsBoolean()); } + + [Theory] + [InlineData("11n ** 711111111n")] + [InlineData("2n ** 10000000n")] + [InlineData("100n ** 1000000n")] + public void ExponentiationShouldThrowForExcessiveSize(string expression) + { + var engine = new Engine(options => + { + options.TimeoutInterval(TimeSpan.FromSeconds(5)); + options.LimitMemory(16_000_000); + }); + + var ex = Assert.Throws(() => engine.Evaluate(expression)); + Assert.Contains("Maximum BigInt size exceeded", ex.Message); + } + + [Theory] + [InlineData("11n ** 711111111n")] + [InlineData("2n ** 10000000n")] + public void ExponentiationAssignmentShouldThrowForExcessiveSize(string expression) + { + var engine = new Engine(options => + { + options.TimeoutInterval(TimeSpan.FromSeconds(5)); + options.LimitMemory(16_000_000); + }); + + // Convert "a ** b" to "x = a; x **= b" to test assignment path + var parts = expression.Split(new[] { " ** " }, StringSplitOptions.None); + var script = $"var x = {parts[0]}; x **= {parts[1]}; x"; + + var ex = Assert.Throws(() => engine.Evaluate(script)); + Assert.Contains("Maximum BigInt size exceeded", ex.Message); + } + + [Theory] + [InlineData("2n ** 100n", "1267650600228229401496703205376")] + [InlineData("3n ** 50n", "717897987691852588770249")] + [InlineData("0n ** 1000000n", "0")] + [InlineData("1n ** 1000000n", "1")] + [InlineData("(-1n) ** 1000001n", "-1")] + public void ExponentiationShouldWorkForReasonableSizes(string expression, string expected) + { + var engine = new Engine(); + var result = engine.Evaluate(expression); + Assert.Equal(expected, result.ToString()); + } + + [Fact] + public void NegativeExponentShouldThrowRangeError() + { + var engine = new Engine(); + var ex = Assert.Throws(() => engine.Evaluate("2n ** -1n")); + Assert.Contains("Exponent must be positive", ex.Message); + } } diff --git a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs index bd2a05d187..facfc3cd75 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs @@ -355,11 +355,20 @@ protected override object EvaluateInternal(EvaluationContext context) else { var exponent = TypeConverter.ToBigInt(rval); - if (exponent > int.MaxValue || exponent < int.MinValue) + if (exponent < 0) { - Throw.TypeError(context.Engine.Realm, "Cannot do exponentiation with exponent not fitting int32"); + Throw.RangeError(context.Engine.Realm, "Exponent must be positive"); } - newLeftValue = JsBigInt.Create(BigInteger.Pow(TypeConverter.ToBigInt(originalLeftValue), (int) exponent)); + + if (exponent > int.MaxValue) + { + Throw.RangeError(context.Engine.Realm, "Maximum BigInt size exceeded"); + } + + var intExponent = (int) exponent; + var baseValue = TypeConverter.ToBigInt(originalLeftValue); + JintBinaryExpression.ValidateBigIntPowSize(context.Engine.Realm, baseValue, intExponent); + newLeftValue = JsBigInt.Create(BigInteger.Pow(baseValue, intExponent)); } break; diff --git a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs index 94b6449bc6..ab1344e8c4 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs @@ -250,6 +250,23 @@ internal static void AssertValidBigIntArithmeticOperands(JsValue left, JsValue r } } + /// + /// Validates that BigInteger.Pow(base, exponent) won't produce an excessively large result. + /// Limits result to ~1 million bits (~125 KB) to prevent memory exhaustion. + /// + internal static void ValidateBigIntPowSize(Realm realm, BigInteger baseValue, int exponent) + { + if (exponent > 0) + { + var absBase = BigInteger.Abs(baseValue); + if (absBase > BigInteger.One + && (double) exponent * BigInteger.Log(absBase, 2.0) > 1_000_000) + { + Throw.RangeError(realm, "Maximum BigInt size exceeded"); + } + } + } + private sealed class StrictlyEqualBinaryExpression : JintBinaryExpression { public StrictlyEqualBinaryExpression(NonLogicalBinaryExpression expression) : base(expression) @@ -881,11 +898,15 @@ protected override object EvaluateInternal(EvaluationContext context) Throw.RangeError(context.Engine.Realm, "Exponent must be positive"); } - if (exponent > int.MaxValue || exponent < int.MinValue) + if (exponent > int.MaxValue) { - Throw.TypeError(context.Engine.Realm, "Exponent does not fit 32bit range"); + Throw.RangeError(context.Engine.Realm, "Maximum BigInt size exceeded"); } - result = JsBigInt.Create(BigInteger.Pow(left.AsBigInt(), (int) exponent)); + + var intExponent = (int) exponent; + var baseValue = left.AsBigInt(); + ValidateBigIntPowSize(context.Engine.Realm, baseValue, intExponent); + result = JsBigInt.Create(BigInteger.Pow(baseValue, intExponent)); } return result;