Skip to content

Commit

Permalink
Fix parsing of defined? blocks for complex expressions
Browse files Browse the repository at this point in the history
YARD evaluates these blocks which can lead to arbitrary execution
of code. Thanks to Nelson Elhage <nelhage@nelhage.com> for
reporting this issue.

Fixes #1177
  • Loading branch information
lsegal committed Jul 18, 2018
1 parent 44ca65d commit d7859cb
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 7 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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 <nelhage@nelhage.com> 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
Expand Down
17 changes: 11 additions & 6 deletions lib/yard/handlers/ruby/class_condition_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion lib/yard/handlers/ruby/legacy/class_condition_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
19 changes: 19 additions & 0 deletions spec/handlers/class_condition_handler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit d7859cb

Please sign in to comment.