Skip to content

Commit 2d01bd1

Browse files
xoofxCopilot
andcommitted
Harden expression evaluation resource bounds
Co-authored-by: Copilot <[email protected]>
1 parent 9856321 commit 2d01bd1

File tree

4 files changed

+163
-37
lines changed

4 files changed

+163
-37
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
text(5,1) : error : Exceeding number of iteration limit `1000` for loop statement.
1+
text(5,11) : error : Range expression exceeds LoopLimit `1000`.

src/Scriban.Tests/TestRuntime.cs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,58 @@ public void ResetShouldClearCumulativeRenderOutputTracking()
121121
Assert.AreEqual("xy", smallTemplate.Render(context));
122122
}
123123

124+
[Test]
125+
public void StringMultiplicationShouldRespectLimitToString()
126+
{
127+
var context = new TemplateContext
128+
{
129+
LimitToString = 5
130+
};
131+
var template = Template.Parse("{{ 'ab' * 3 }}");
132+
133+
var exception = Assert.Throws<ScriptRuntimeException>(() => template.Render(context));
134+
135+
StringAssert.Contains("LimitToString", exception!.Message);
136+
}
137+
138+
[Test]
139+
public void BigIntegerShiftShouldRejectOversizedAmounts()
140+
{
141+
var template = Template.Parse("{{ 1 << 1048577 }}");
142+
143+
var exception = Assert.Throws<ScriptRuntimeException>(() => template.Render());
144+
145+
StringAssert.Contains("Shift amount", exception!.Message);
146+
}
147+
148+
[Test]
149+
public void RangeExpressionShouldRespectLoopLimit()
150+
{
151+
var context = new TemplateContext
152+
{
153+
LoopLimit = 5
154+
};
155+
var template = Template.Parse("1..6", lexerOptions: new LexerOptions { Mode = ScriptMode.ScriptOnly });
156+
157+
var exception = Assert.Throws<ScriptRuntimeException>(() => template.Evaluate(context));
158+
159+
StringAssert.Contains("LoopLimit", exception!.Message);
160+
}
161+
162+
[Test]
163+
public void ArrayJoinShouldRespectLimitToString()
164+
{
165+
var context = new TemplateContext
166+
{
167+
LimitToString = 3
168+
};
169+
var template = Template.Parse("{{ ['ab', 'cd'] | array.join '' }}");
170+
171+
var exception = Assert.Throws<ScriptRuntimeException>(() => template.Render(context));
172+
173+
StringAssert.Contains("LimitToString", exception!.Message);
174+
}
175+
124176
[Test]
125177
public void TestAssignValToDictionary()
126178
{
@@ -1456,7 +1508,7 @@ public void TestNestedLoopLimit()
14561508

14571509
// This should throw because the inner loop has 3 iterations, exceeding limit of 2
14581510
var exception = Assert.Throws<ScriptRuntimeException>(() => template.Render(context));
1459-
Assert.That(exception.Message, Does.Contain("Exceeding number of iteration limit `2` for loop statement"));
1511+
Assert.That(exception.Message, Does.Contain("LoopLimit `2`"));
14601512
}
14611513

14621514
[Test]
@@ -1479,7 +1531,7 @@ public void TestNestedLoopLimitSimple()
14791531

14801532
// This should throw because inner loop has 4 iterations > limit of 3
14811533
var exception = Assert.Throws<ScriptRuntimeException>(() => template.Render(context));
1482-
Assert.That(exception.Message, Does.Contain("Exceeding number of iteration limit `3` for loop statement"));
1534+
Assert.That(exception.Message, Does.Contain("LoopLimit `3`"));
14831535
}
14841536

14851537
[Test]
@@ -1572,7 +1624,7 @@ public void TestNestedLoopLimitIndependentCounters()
15721624

15731625
// This should throw on the inner loop (5 iterations > 3 limit)
15741626
var exception = Assert.Throws<ScriptRuntimeException>(() => template.Render(context));
1575-
Assert.That(exception.Message, Does.Contain("Exceeding number of iteration limit `3` for loop statement"));
1627+
Assert.That(exception.Message, Does.Contain("LoopLimit `3`"));
15761628
}
15771629

15781630
[Test]

src/Scriban/Functions/ArrayFunctions.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ public static string Join(TemplateContext context, SourceSpan span, IEnumerable
389389
{
390390
if (afterFirst)
391391
{
392+
ValidateJoinedTextLength(context, span, text.Length, delimiter?.Length ?? 0);
392393
text.Append(delimiter);
393394
}
394395

@@ -400,12 +401,21 @@ public static string Join(TemplateContext context, SourceSpan span, IEnumerable
400401
item = context.ObjectToString(result);
401402
}
402403

404+
ValidateJoinedTextLength(context, span, text.Length, item?.Length ?? 0);
403405
text.Append(item);
404406
afterFirst = true;
405407
}
406408
return text.ToString();
407409
}
408410

411+
private static void ValidateJoinedTextLength(TemplateContext context, SourceSpan span, int currentLength, int additionalLength)
412+
{
413+
if (context.LimitToString > 0 && additionalLength > 0 && (long)currentLength + additionalLength > context.LimitToString)
414+
{
415+
throw new ScriptRuntimeException(span, $"Joined string exceeds LimitToString `{context.LimitToString}`.");
416+
}
417+
}
418+
409419
/// <summary>
410420
/// Returns the last element of the input `list`.
411421
/// </summary>
@@ -806,4 +816,4 @@ public override string ToString()
806816
}
807817
}
808818
}
809-
}
819+
}

0 commit comments

Comments
 (0)