diff --git a/CHANGELOG.md b/CHANGELOG.md index b6f8932c8..c96165365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# master + +# [0.9.15] - July 17th, 2018 + +[0.9.15]: https://github.com/lsegal/yard/compare/v0.9.14...v0.9.15 + +- Fixed security issue in parsing of Ruby code that could allow for arbitrary + execution. Credit to Nelson Elhage for discovering this + issue. + # [0.9.14] - June 2nd, 2018 [0.9.14]: https://github.com/lsegal/yard/compare/v0.9.13...v0.9.14 diff --git a/lib/yard/handlers/ruby/class_condition_handler.rb b/lib/yard/handlers/ruby/class_condition_handler.rb index 6968859bb..1c1170045 100644 --- a/lib/yard/handlers/ruby/class_condition_handler.rb +++ b/lib/yard/handlers/ruby/class_condition_handler.rb @@ -52,12 +52,17 @@ def parse_condition # defined? keyword used, let's see if we can look up the name # in the registry, then we'll try using Ruby's powers. eval() is not # *too* dangerous here since code is not actually executed. - name = statement.condition[0].source - obj = YARD::Registry.resolve(namespace, name, true) - begin - condition = true if obj || Object.instance_eval("defined? #{name}") - rescue SyntaxError, NameError - condition = false + arg = statement.condition.first + + if arg.type == :var_ref + name = arg.source + obj = YARD::Registry.resolve(namespace, name, true) + + begin + condition = true if obj || (name && Object.instance_eval("defined? #{name}")) + rescue SyntaxError, NameError + condition = false + end end when :var_ref var = statement.condition[0] diff --git a/lib/yard/handlers/ruby/legacy/class_condition_handler.rb b/lib/yard/handlers/ruby/legacy/class_condition_handler.rb index 5ef57eec1..ef79b7479 100644 --- a/lib/yard/handlers/ruby/legacy/class_condition_handler.rb +++ b/lib/yard/handlers/ruby/legacy/class_condition_handler.rb @@ -41,7 +41,7 @@ def parse_condition case statement.tokens[1..-1].to_s.strip when /^(\d+)$/ condition = $1 != "0" - when /^defined\?\s*\(?(.+?)\)?$/ + when /^defined\?\s*\(?\s*([A-Za-z0-9:_]+?)\s*\)?$/ # defined? keyword used, let's see if we can look up the name # in the registry, then we'll try using Ruby's powers. eval() is not # *too* dangerous here since code is not actually executed. diff --git a/spec/handlers/class_condition_handler_spec.rb b/spec/handlers/class_condition_handler_spec.rb index f7cdd94aa..51cfb738f 100644 --- a/spec/handlers/class_condition_handler_spec.rb +++ b/spec/handlers/class_condition_handler_spec.rb @@ -65,4 +65,23 @@ def no_undoc_error(code) eof no_undoc_error "if caller.none? { |l| l =~ %r{lib/rails/generators\\.rb:(\\d+):in `lookup!'$} }; end" end + + it "only parses identifiers or namespaces from defined? expressions" do + module A + @@value = nil + def self.b(value) @@value = value end + def self.value; @@value end + end + + YARD.parse_string <<-eof + if defined? A.b(42).to_i + class Foo; end + else + class Bar; end + end + eof + expect(A.value).to be_nil + expect(YARD::Registry.at('Foo')).not_to be_nil + expect(YARD::Registry.at('Bar')).not_to be_nil + end end