diff --git a/lib/liquid/block.rb b/lib/liquid/block.rb index eed0475..d9547c7 100644 --- a/lib/liquid/block.rb +++ b/lib/liquid/block.rb @@ -1,5 +1,7 @@ module Liquid class Block < Tag + MAX_DEPTH = 100 + def initialize(tag_name, markup, options) super @blank = true @@ -48,17 +50,25 @@ module Liquid protected def parse_body(body, tokens) - body.parse(tokens, parse_context) do |end_tag_name, end_tag_params| - @blank &&= body.blank? + if parse_context.depth >= MAX_DEPTH + raise StackLevelError, "Nesting too deep".freeze + end + parse_context.depth += 1 + begin + body.parse(tokens, parse_context) do |end_tag_name, end_tag_params| + @blank &&= body.blank? - return false if end_tag_name == block_delimiter - unless end_tag_name - raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name)) + return false if end_tag_name == block_delimiter + unless end_tag_name + raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name)) + end + + # this tag is not registered with the system + # pass it to the current block for special handling or error reporting + unknown_tag(end_tag_name, end_tag_params, tokens) end - - # this tag is not registered with the system - # pass it to the current block for special handling or error reporting - unknown_tag(end_tag_name, end_tag_params, tokens) + ensure + parse_context.depth -= 1 end true diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index 566c5c6..34051a1 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -89,7 +89,7 @@ module Liquid # Push new local scope on the stack. use Context#stack instead def push(new_scope = {}) @scopes.unshift(new_scope) - raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100 + raise StackLevelError, "Nesting too deep".freeze if @scopes.length > Block::MAX_DEPTH end # Merge a hash of variables in the current local scope diff --git a/lib/liquid/parse_context.rb b/lib/liquid/parse_context.rb index 34986dd..abcdaeb 100644 --- a/lib/liquid/parse_context.rb +++ b/lib/liquid/parse_context.rb @@ -1,12 +1,13 @@ module Liquid class ParseContext - attr_accessor :locale, :line_number, :trim_whitespace + attr_accessor :locale, :line_number, :trim_whitespace, :depth attr_reader :partial, :warnings, :error_mode def initialize(options = {}) @template_options = options ? options.dup : {} @locale = @template_options[:locale] ||= I18n.new @warnings = [] + self.depth = 0 self.partial = false end diff --git a/test/integration/security_test.rb b/test/integration/security_test.rb index 0357498..f603ff0 100644 --- a/test/integration/security_test.rb +++ b/test/integration/security_test.rb @@ -63,4 +63,18 @@ class SecurityTest < Minitest::Test assert_equal [], (Symbol.all_symbols - current_symbols) end + + def test_max_depth_nested_blocks_does_not_raise_exception + depth = Liquid::Block::MAX_DEPTH + code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth + assert_equal "rendered", Template.parse(code).render! + end + + def test_more_than_max_depth_nested_blocks_raises_exception + depth = Liquid::Block::MAX_DEPTH + 1 + code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth + assert_raises(Liquid::StackLevelError) do + Template.parse(code).render! + end + end end # SecurityTest diff --git a/test/integration/tags/include_tag_test.rb b/test/integration/tags/include_tag_test.rb index 25af662..1d854b0 100644 --- a/test/integration/tags/include_tag_test.rb +++ b/test/integration/tags/include_tag_test.rb @@ -137,7 +137,7 @@ class IncludeTagTest < Minitest::Test Liquid::Template.file_system = infinite_file_system.new - assert_raises(Liquid::StackLevelError, SystemStackError) do + assert_raises(Liquid::StackLevelError) do Template.parse("{% include 'loop' %}").render! end end