Skip to content

Commit 1e4316b

Browse files
committed
(PUP-11772) Resolve Security/Eval
Both actions and functions/data types already define arbitrary code and are loaded from trusted locations, so using eval isn't any worse. I updated the ActionBuilder to delegate specific methods to the action. For example, if an action calls the DSL method `summary "something"`, then the ActionBuilder will call the corresponding setter on the Action, e.g. Action#summary = "something". The Action code is bit more complicated because the arity of the block passed to `when_invoked=` may be 0, positive or negative, depending on whether it accepts optional arguments. Since we don't support Ruby 1.8 - 2.6, it could be improved in the future to not call `eval`, but I didn't feel like bothering.
1 parent 283ba4c commit 1e4316b

File tree

6 files changed

+11
-22
lines changed

6 files changed

+11
-22
lines changed

.rubocop_todo.yml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -918,14 +918,6 @@ Naming/VariableName:
918918
Naming/VariableNumber:
919919
Enabled: false
920920

921-
Security/Eval:
922-
Exclude:
923-
- 'lib/puppet/interface/action.rb'
924-
- 'lib/puppet/interface/action_builder.rb'
925-
- 'lib/puppet/pops/loader/ruby_data_type_instantiator.rb'
926-
- 'lib/puppet/pops/loader/ruby_function_instantiator.rb'
927-
- 'lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb'
928-
929921
# Configuration parameters: EnforcedStyle, AllowModifiersOnSymbols.
930922
# SupportedStyles: inline, group
931923
Style/AccessModifierDeclarations:

lib/puppet/interface/action.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,14 @@ def #{@name}(#{decl.join(", ")})
265265
end
266266
WRAPPER
267267

268+
# It should be possible to rewrite this code to use `define_method`
269+
# instead of `class/instance_eval` since Ruby 1.8 is long dead.
268270
if @face.is_a?(Class)
269-
@face.class_eval do eval wrapper, nil, file, line end
271+
@face.class_eval do eval wrapper, nil, file, line end # rubocop:disable Security/Eval
270272
@face.send(:define_method, internal_name, &block)
271273
@when_invoked = @face.instance_method(name)
272274
else
273-
@face.instance_eval do eval wrapper, nil, file, line end
275+
@face.instance_eval do eval wrapper, nil, file, line end # rubocop:disable Security/Eval
274276
@face.meta_def(internal_name, &block)
275277
@when_invoked = @face.method(name).unbind
276278
end

lib/puppet/interface/action_builder.rb

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
# within the context of a new instance of this class.
66
# @api public
77
class Puppet::Interface::ActionBuilder
8+
extend Forwardable
9+
810
# The action under construction
911
# @return [Puppet::Interface::Action]
1012
# @api private
@@ -142,15 +144,8 @@ def render_as(value = nil)
142144
property = setter.to_s.chomp('=')
143145

144146
unless method_defined? property
145-
# Using eval because the argument handling semantics are less awful than
146-
# when we use the define_method/block version. The later warns on older
147-
# Ruby versions if you pass the wrong number of arguments, but carries
148-
# on, which is totally not what we want. --daniel 2011-04-18
149-
eval <<-METHOD
150-
def #{property}(value)
151-
@action.#{property} = value
152-
end
153-
METHOD
147+
# ActionBuilder#<property> delegates to Action#<setter>
148+
def_delegator :@action, setter, property
154149
end
155150
end
156151

lib/puppet/pops/loader/ruby_data_type_instantiator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def self.create(loader, typed_name, source_ref, ruby_code_string)
2020
# make the private loader available in a binding to allow it to be passed on
2121
loader_for_type = loader.private_loader
2222
here = get_binding(loader_for_type)
23-
created = eval(ruby_code_string, here, source_ref, 1)
23+
created = eval(ruby_code_string, here, source_ref, 1) # rubocop:disable Security/Eval
2424
unless created.is_a?(Puppet::Pops::Types::PAnyType)
2525
raise ArgumentError, _("The code loaded from %{source_ref} did not produce a data type when evaluated. Got '%{klass}'") % { source_ref: source_ref, klass: created.class }
2626
end

lib/puppet/pops/loader/ruby_function_instantiator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def self.create(loader, typed_name, source_ref, ruby_code_string)
2020
# make the private loader available in a binding to allow it to be passed on
2121
loader_for_function = loader.private_loader
2222
here = get_binding(loader_for_function)
23-
created = eval(ruby_code_string, here, source_ref, 1)
23+
created = eval(ruby_code_string, here, source_ref, 1) # rubocop:disable Security/Eval
2424
unless created.is_a?(Class)
2525
raise ArgumentError, _("The code loaded from %{source_ref} did not produce a Function class when evaluated. Got '%{klass}'") % { source_ref: source_ref, klass: created.class }
2626
end

lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def self.create(loader, typed_name, source_ref, ruby_code_string)
3838
# This will do the 3x loading and define the "function_<name>" and "real_function_<name>" methods
3939
# in the anonymous module used to hold function definitions.
4040
#
41-
func_info = eval(ruby_code_string, here, source_ref, 1)
41+
func_info = eval(ruby_code_string, here, source_ref, 1) # rubocop:disable Security/Eval
4242

4343
# Validate what was loaded
4444
unless func_info.is_a?(Hash)

0 commit comments

Comments
 (0)