diff --git a/lib/gitlab/ci/config/extendable/collection.rb b/lib/gitlab/ci/config/extendable/collection.rb index 123219c90f2..13d81987b7a 100644 --- a/lib/gitlab/ci/config/extendable/collection.rb +++ b/lib/gitlab/ci/config/extendable/collection.rb @@ -8,6 +8,7 @@ module Gitlab ExtensionError = Class.new(StandardError) InvalidExtensionError = Class.new(ExtensionError) CircularDependencyError = Class.new(ExtensionError) + NestingTooDeepError = Class.new(ExtensionError) def initialize(hash) @hash = hash.to_h.deep_dup diff --git a/lib/gitlab/ci/config/extendable/entry.rb b/lib/gitlab/ci/config/extendable/entry.rb index f14d2c1817c..d1fd8005b71 100644 --- a/lib/gitlab/ci/config/extendable/entry.rb +++ b/lib/gitlab/ci/config/extendable/entry.rb @@ -3,6 +3,8 @@ module Gitlab class Config module Extendable class Entry + MAX_NESTING_LEVELS = 10 + attr_reader :key def initialize(key, context, parent = nil) @@ -10,7 +12,9 @@ module Gitlab @context = context @parent = parent - raise StandardError, 'Invalid entry key!' unless @context.key?(@key) + unless @context.key?(@key) + raise StandardError, 'Invalid entry key!' + end end def extensible? @@ -31,8 +35,8 @@ module Gitlab value.fetch(:extends).to_s.to_sym if extensible? end - def path - Array(@parent&.path).compact.push(key) + def ancestors + @ancestors ||= Array(@parent&.ancestors) + Array(@parent&.key) end def extend! @@ -48,6 +52,11 @@ module Gitlab "Invalid base hash in extended `#{key}`!" end + if nesting_too_deep? + raise Extendable::Collection::NestingTooDeepError, + "`extends` nesting too deep in `#{key}`!" + end + if circular_dependency? raise Extendable::Collection::CircularDependencyError, "Circular dependency detected in extended `#{key}`!" @@ -58,8 +67,12 @@ module Gitlab private + def nesting_too_deep? + ancestors.count > MAX_NESTING_LEVELS + end + def circular_dependency? - path.count(key) > 1 + ancestors.include?(key) end def unknown_extension? diff --git a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb index 2726d91f6d6..8afdff99c7f 100644 --- a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb +++ b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb @@ -62,12 +62,17 @@ describe Gitlab::Ci::Config::Extendable::Entry do end end - describe '#path' do - it 'returns inheritance chain path' do - parent = described_class.new(:test, test: { extends: 'something' }) - child = described_class.new(:job, { job: { script: 'something' } }, parent) + describe '#ancestors' do + let(:parent) do + described_class.new(:test, test: { extends: 'something' }) + end - expect(child.path).to eq [:test, :job] + let(:child) do + described_class.new(:job, { job: { script: 'something' } }, parent) + end + + it 'returns ancestors keys' do + expect(child.ancestors).to eq [:test] end end @@ -196,5 +201,24 @@ describe Gitlab::Ci::Config::Extendable::Entry do .to raise_error(StandardError, /Circular dependency detected/) end end + + context 'when nesting level is too deep' do + before do + stub_const("#{described_class}::MAX_NESTING_LEVELS", 0) + end + + let(:hash) do + { + first: { script: 'my value' }, + second: { extends: 'first' }, + test: { extends: 'second' } + } + end + + it 'raises an error' do + expect { subject.extend! } + .to raise_error(Gitlab::Ci::Config::Extendable::Collection::NestingTooDeepError) + end + end end end